作者其他文章
- AOSP | Android 11 Settings 开发(01) 环境搭建
- AOSP | Android 11 Framework 修改记录(持续更新)
- AOSP | Android 9 Framework 修改记录(持续更新)
- AOSP | Android 9 由壁纸切换带来的手机主题变更及问题出现)
- AOSP | Android 9 过滤Recents
- AOSP | APP源码移植到系统源码中进行编译
- 后端 | 沙箱环境下实现支付宝网站支付
- 前端 | vue-echarts渲染时视图模糊的解决办法
1、问题来源
由于我是做平板方案的,前段时间遇到一个问题,说的是第三方App进入全屏模式时,底部导航栏会遮挡住App下方的内容,当时的想法是为什么有的App进入全屏模式会自动留出导航栏的位置,并且将自己底部操作栏顶上去,这样子就能既触发App底部操作栏事件,又能操作底部导航栏;而有的App的底部操作栏则直接被导航栏覆盖。我能否读取当前App是否进入全屏从而修改导航栏的位置,动手过后发现很复杂,而且难以适配。后面换了一个想法,就在设置App中直接让用户手动进行设置,控制导航栏是否显示或者隐藏,也可以理解为三指导航以及虚拟按键导航的相互切换。
2、问题解析
由于产品是自带了自己研发的设置App,所以主要讲一下Framework层的修改,而不涉及设置App方面的修改。
2-1 显示与隐藏的接口开发
在之前的文章中,我修改过导航栏的状态,涉及沉浸式导航栏与导航栏延迟消失,知道导航栏的创建在packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
});
}
如果我们想要显示与隐藏导航栏的话,可以再此处添加接口;
显示的话,很好理解,就是上述的createNavigationBar()
/**
* Controls the display of navigation bar.
*
* @Author wayhoi
* @Date 2022-03-13 16:43
*/
private void displayOfNavigationBar() {
createNavigationBar();
}
隐藏的话,可以通过packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java中的destroy()中查阅到
public void destroy() {
// Begin old BaseStatusBar.destroy().
mContext.unregisterReceiver(mBannerActionBroadcastReceiver);
mLockscreenUserManager.destroy();
try {
mNotificationListener.unregisterAsSystemService();
} catch (RemoteException e) {
// Ignore.
}
mEntryManager.destroy();
// End old BaseStatusBar.destroy().
if (mStatusBarWindow != null) {
mWindowManager.removeViewImmediate(mStatusBarWindow);
mStatusBarWindow = null;
}
if (mNavigationBarView != null) {
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
}
mContext.unregisterReceiver(mBroadcastReceiver);
mContext.unregisterReceiver(mDemoReceiver);
mAssistManager.destroy();
if (mQSPanel != null && mQSPanel.getHost() != null) {
mQSPanel.getHost().destroy();
}
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
mDeviceProvisionedController.removeCallback(mUserSetupObserver);
Dependency.get(ConfigurationController.class).removeCallback(this);
mZenController.removeCallback(this);
mAppOpsListener.destroy();
}
通过阅读代码,可以发现,隐藏(这里用去除来描述会更恰当)的方法如下:
/**
* Controls the hiding of navigation bar.
*
* @Author wayhoi
* @Date 2022-03-13 17:12
*/
private void hidingOfNavigationBar() {
if (mWindowManager != null && mNavigationBarView != null) {
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
}
}
于是,从源码中,我们就将NavigationBar的显示与隐藏接口写好了,接下来就是考虑调用。
2-2 触发接口
这里我用的是通过广播来进行触发,设置应用内通过开关按钮来进行发送显示与关闭的广播。
com/example/settings/operation/BarOperation.java
/**
* Action to display of navigation bar.
*/
public static final String ACTION_DISPLAY_OF_NAVIGATION_BAR = "com.example.display_of_navigation_bar";
/**
* Action to hiding of navigation bar.
*/
public static final String ACTION_HIDING_OF_NAVIGATION_BAR = "com.example.hiding_of_navigation_bar";
/**
* Intent to display of navigation bar.
*/
private final Intent INTENT_DISPLAY_OF_NAVIGATION_BAR = new Intent(ACTION_DISPLAY_OF_NAVIGATION_BAR);
/**
* Action to hiding of navigation bar.
*/
private final Intent INTENT_HIDING_OF_NAVIGATION_BAR = new Intent(ACTION_HIDING_OF_NAVIGATION_BAR);
/**
* Controls the hiding of navigation bar.
*
* @Author wayhoi
* @Date 2022-03-13 17:18
*/
public void switchNavigationBar(boolean isChecked) {
context.sendBroadcast(isChecked ? INTENT_DISPLAY_OF_NAVIGATION_BAR : INTENT_HIDING_OF_NAVIGATION_BAR);
}
同时让packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java接收广播:
+ /**
+ * Action to display of navigation bar.
+ */
+ public static final String ACTION_DISPLAY_OF_NAVIGATION_BAR = "com.example.display_of_navigation_bar";
+
+ /**
+ * Action to hiding of navigation bar.
+ */
+ public static final String ACTION_HIDING_OF_NAVIGATION_BAR = "com.example.hiding_of_navigation_bar";
protected void makeStatusBarView() {
//省略代码
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
+ filter.addAction(ACTION_NAVIGATION_HIDE);
+ filter.addAction(ACTION_NAVIGATION_SHOW);
//省略代码
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//省略代码
+ else if (ACTION_NAVIGATION_HIDE.equals(action)) {
+ hidingOfNavigationBar();
+ } else if (ACTION_NAVIGATION_SHOW.equals(action)) {
+ displayOfNavigationBar();
}
}
};
到这里,我们就已经写完了;编译、刷机、测试,发现切换设置中的开关时,已经有效地可以控制导航栏的显示与隐藏;
但是有个问题,当我隐藏导航栏后,重新开机,发现导航栏又会出现,说明当前的代码无法记录导航栏的状态。于是我想到,可以用数据库来记录状态。
2-3 状态持久化
我们继续编写,修改frameworks/base/core/java/android/provider/Settings.java
public final class Settings {
//省略代码
public static final class System extends NameValueTable {
//省略代码
+ /** {@hide} */
+ public static final String HIDING_OF_NAVIGATION_BAR = "hiding_of_navigation_bar";
+ /** {@hide} */
+ public static final Uri HIDING_OF_NAVIGATION_BAR_URI =getUriFor(HIDING_OF_NAVIGATION_BAR);
+ private static final Validator HIDING_OF_NAVIGATION_BAR_VALIDATOR = URI_VALIDATOR;
//省略代码
public static final String[] SETTINGS_TO_BACKUP = {
+ HIDING_OF_NAVIGATION_BAR
//省略代码
}
//省略代码
public static final Set<String> PUBLIC_SETTINGS = new ArraySet<>();
static {
+ PUBLIC_SETTINGS.add(HIDING_OF_NAVIGATION_BAR);
//省略代码
}
//省略代码
public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
static {
+ VALIDATORS.put(HIDING_OF_NAVIGATION_BAR, HIDING_OF_NAVIGATION_BAR_VALIDATOR);
//省略代码
}
//省略代码
}
//省略代码
}
然后去frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java初始化HIDING_OF_NAVIGATION_BAR的初始值,这里我们定义规则:0为隐藏,1为显示
@Deprecated
class DatabaseHelper extends SQLiteOpenHelper {
//省略代码
private void loadSystemSettings(SQLiteDatabase db) {
SQLiteStatement stmt = null;
try {
stmt = db.compileStatement("INSERT OR IGNORE INTO system(name,value)"
+ " VALUES(?,?);");
+ /// M: modify by wayhoi, init the value of navigation bar state. @{
+ loadIntegerSetting(stmt, Settings.System.HIDING_OF_NAVIGATION_BAR, 1);
+ /// @}
//省略代码
}
}
//省略代码
}
往后,我们在调用packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java中的**displayOfNavigationBar()和hidingOfNavigationBar()**时,便将新值写入数据库,于是我们修改接口:
protected void makeStatusBarView() {
//省略代码
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
if (showNav) {
/// M: modify by wayhoi, controls the display and hiding of the navigation bar. 1 is display, and 0 is hiding. @{
if (1 == Settings.System.getInt(context.getContentResolver(), Settings.System.HIDING_OF_NAVIGATION_BAR, 0)) {
createNavigationBar();
}
/// @}
}
} catch (RemoteException ex) {
// no window manager? good luck with that
}
//省略代码
}
/**
* Controls the display of navigation bar.
*
* @Author wayhoi
* @Date 2022-03-13 16:43
*/
private void displayOfNavigationBar() {
createNavigationBar();
+ Settings.System.putInt(context.getContentResolver(), Settings.System.HIDING_OF_NAVIGATION_BAR, 1);
}
/**
* Controls the hiding of navigation bar.
*
* @Author wayhoi
* @Date 2022-03-13 17:12
*/
private void hidingOfNavigationBar() {
if (mWindowManager != null && mNavigationBarView != null) {
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
+ Settings.System.putInt(context.getContentResolver(), Settings.System.HIDING_OF_NAVIGATION_BAR, 0);
}
}
同时我们在设置App中,监听HIDING_OF_NAVIGATION_BAR的变化
public void onResume() {
super.onResumer();
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.HIDING_OF_NAVIGATION_BAR),
+ true,
+ navigationObserver
+ );
}
+ /**
+ * 监听导航栏变化
+ *
+ * @Author wayhoi
+ * @Date 2022-03-13 18:31
+ */
+ private ContentObserver navigationObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean isDisplay = Settings.System.getInt(context.getContentResolver(), Settings.System.HIDING_OF_NAVIGATION_BAR, 0) == 1;
+ switchOfNavigationBar.setChecked(isDisplay);
+ }
+ };
总结
刷机测试,发现符合预期。