
做RK3588方案的朋友不管是做广告机、信息发布屏、一体机还是车载中控估计都遇到过这个需求系统跑起来之后想办法把顶部的状态栏和底部那条导航栏整个干掉让界面干干净净只保留自己的应用。需求听起来特别简单但真正动手之后才会发现里面坑多得能让你怀疑人生。我在这块踩过的坑不算少今天就把RK3588平台、Android12系统上“彻底隐藏导航栏和状态栏”的完整思路和源码级修改方案整理出来给正在折腾这件事的朋友一个能直接参考的路线。先说清楚“彻底隐藏”和普通隐藏的区别。很多方案用沉浸式模式Immersive Mode用户一滑动屏幕边缘系统栏又弹出来了这在商用设备上是不能接受的。我们要的是“系统起来后导航栏和状态栏彻底消失不占空间、不响应任何触摸、永远不再出现”这必须从SystemUI和框架层源码层面去改。本文面向的是有Android系统定制经验、手上拿着RK3588开发板、用的是Android12源码的工程师零基础的可以先补一下AOSP编译和RK平台编译环境的知识再来看这篇。1. 为什么隐藏系统栏这件事在项目里总是反复折腾1.1 “隐藏”和“彻底隐藏”是两个维度的问题在很多工程师的认知里系统栏隐藏不就是setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)一行代码的事吗这想法我在刚接触这个需求的时候也有过但实际测试几天就会发现问题。常规的隐藏方案本质上是“临时隐藏”系统栏View还在窗口层级里只是被设置成了不可见状态。遇到IME弹出、焦点切换、旋转屏幕、按电源键唤醒这些场景系统栏会被SystemUI重新拉出来。而“彻底隐藏”是要做到系统栏组件根本不被创建或者创建后直接被干掉不再参与窗口布局和触摸分发空间和事件全部释放给应用层。这两件事的代码路径完全不一样前者是应用层API能搞定的后者必须在SystemUI和服务端动手。以RK3588这类工控/商业设备的使用场景来说设备上跑的是固定应用比如数字标牌播放器、自助查询终端用户没有任何理由需要看到系统导航栏。这时候如果系统栏还能被划出来一是影响视觉统一性二是用户真的会误触返回键或者Home键导致应用退出这在营业场所是很严重的事故。所以“彻底隐藏”不只是体验问题更是产品稳定性问题。1.2 RK3588 Android12又有哪些新难点RK3588这颗芯片目前是安卓商显主控里的主力8核、性能强、接口全但它在Android12上有个比较麻烦的特点芯片厂商在AOSP基础上做了大量BSP定制SystemUI和框架层都有patch合入。这就导致你百度来的方法不一定直接适用代码位置可能不一样log行为也可能不一样。再加上Android12本身对系统栏的管控和Android10/11相比变化很大。Android12引入了edge-to-edge enforcement强制要求应用内容延伸到系统栏后面系统栏的显示策略由WindowManager统一调度SystemUI部分代码做了重构比如PhoneStatusBar被拆分成StatusBar和NavigationBar两个更清晰的组件导航栏的创建逻辑也不再只由config_showNavigationBar一个开关决定。所以沿用老方案改config.xml的config_showNavigationBar在部分Android12版本上根本无效因为navigation_bar窗口不归这个开关管了。这就引出一个关键判断在RK3588的Android12上“彻底隐藏”必须走源码修改属性控制结合的路线并且要把SystemUI的创建逻辑、PhoneWindowManager的窗口策略、以及配置文件三个层面的开关全部对齐才能达到真正稳定的效果。2. 隐藏系统栏的常见套路先看清它们各自的底牌2.1 方案对比别急着动手先把策略定清楚我在项目里尝试过不止一种隐藏方案这里先做一个整体对比帮你快速判断哪条路适合你自己的场景。方案实现层级隐藏效果稳定性开发成本适用场景应用层沉浸模式应用临时隐藏可滑动唤出低极低普通App内全屏Settings.Global独立控制系统设置隐藏后仍占布局空间中低快速验证调试修改config_showNavigationBar框架资源部分版本有效导航栏不创建中低老版本AndroidOverlay/RRO覆盖资源资源覆盖导航栏高度归零不显示较高中量产定制ROMSystemUI源码移除或禁用SystemUI彻底隐藏不创建不响应高高商用设备定制PhoneWindowManager强制策略Framework服务彻底隐藏窗口级拦截高高可靠性要求极高场景表格里顺序大概就是从“治标”到“治本”的演进。我这里明确说结论如果你的产品要量产、要稳定跑几个月不重启应用层那套一定要放弃直接上SystemUIWMS的组合方案。2.2 沉浸式模式为什么被我一票否决沉浸式模式Immersive Mode在技术社区里讨论度很高我也试过用immersive.full*这个全局属性把所有应用都强制成沉浸模式命令就一行adb shell settings put global policy_control immersive.full*。这个方案的优点是见效快、不涉及代码编译调试阶段非常方便。但它的本质决定了它只能用于调试不能用于量产。原因有三第一沉浸模式从设计上就允许用户通过从屏幕边缘滑动唤出系统栏这是系统级行为你关不掉第二部分应用自己的SurfaceView、视频播放控件会与沉浸模式冲突导致系统栏残留或闪烁第三Android12上沉浸模式配合edge-to-edge后应用内容区的padding计算变得更加依赖系统回调如果应用本身没做好适配界面底部会被内容遮挡或者出现黑条。真机测试一周你就会发现这套方案在商显场景下非常不靠谱。2.3 源码级修改为什么是最终归宿商用设备的核心诉求永远是“可控、可预期、可复制”。源码级修改能让你彻底掌控系统栏的生命周期何时创建、何时销毁、是否响应触摸、窗口是否占用空间全部由你的代码决定。举个例子导航栏本质上是SystemUI进程里的一个NavigationBarView如果我能控制到“这个View在启动时就直接return不创建”那后续不管用户怎么操作都不存在“唤出导航栏”的窗口了因为窗口根本不在。源码修改还有一个额外优势可以留一个“按需打开”的后门。用SystemProperties属性控制平时完全隐藏需要调试或者恢复出厂调试的时候通过adb设置一个属性再重启SystemUI系统栏就回来了。这种灵活度是任何纯配置文件方案都给不了的。3. 源码级修改的核心拆解导航栏、状态栏、窗口策略三线并进3.1 导航栏从SystemUI的创建源头“断根”导航栏的创建入口Android12上主要是在packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java里。这个类会判断shouldHideNavigationBar()之类的条件决定导航栏View是否attach到窗口。我在RK3588的Android12源码里跟踪过整个调用链大致是StatusBar启动后通过NavigationBarCoordinator或NavigationBarEdgeBackPanel等辅助类最终调起NavigationBar的init流程。要在源头断根最简单的做法是在NavigationBar初始化时增加一个系统属性判断比如persist.sys.hidenav。为true时直接不创建View、不注册触摸回调让整个导航栏相关逻辑全部绕行。核心代码思路如下// NavigationBar.java 关键修改示意 import android.os.SystemProperties; private boolean isNavBarHidden() { return SystemProperties.getBoolean(persist.sys.hidenav, false); } Override public void init(NonNull Context context) { if (isNavBarHidden()) { Log.d(NavigationBar, nav bar hidden by persist.sys.hidenav); return; } // ...原有初始化逻辑 }这里有个细节光是return还不够因为某些版本上NavigationBar的onConfigurationChanged、onDarkModeChanged这类回调还是会触发View重建。最稳妥的做法是同时拦截WindowManager.LayoutParams的添加过程或者把NavigationBarFrame替换成空View。我在实战中采用的是“初始化拦截布局替换”双保险具体做法是给导航栏布局容器塞一个高度为0的空View防止任何resize回调引发异常。3.2 状态栏禁用SystemUI里的StatusBar窗口操作状态栏相对导航栏来说稍微好处理一点。Android12上状态栏主要逻辑在packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java和StatusBarViewController.java。我们这的修改核心是让SystemUI在启动时把状态栏窗口的直接操作关掉同时从系统窗口层级里移除状态栏的View。在RK3588的Android12源码上我习惯直接改StatusBar的makeStatusBarView方法在这个方法早期增加属性判断如果persist.sys.hidestatus为true就把状态栏View设置为GONE并且不允许任何代码把它重新设为VISIBLE。但这里要注意状态栏有很多配套功能比如通知图标、时钟、电量这些单纯把View隐藏掉SystemUI内部其他模块可能还会尝试更新这些view引发空指针或者崩溃。所以需要配合把相关的注册回调也一并跳过。实操中我建议重点改两个位置。第一个是StatusBar.java中addStatusBarWindow的判断第二个是CommandQueue中对setSystemUiVisibility的处理。后面这个往往是被忽略的重灾区——应用调用setSystemUiVisibility时SystemUI会收到回调并尝试更新系统栏的可见性如果不改这个回调逻辑哪怕你的状态栏已经隐藏了某次更新又会被拉出来。3.3 PhoneWindowManager从窗口策略层加一道保险到这里可能有人会觉得SystemUI改完就够了。我第一次也是这么想的结果测试时发现偶尔会有“系统栏闪现”的情况排查了很久才发现是框架层的窗口策略在捣鬼。Android12的系统窗口管理有一个核心服务叫PhoneWindowManager路径在frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java。它会根据windowManager的层级配置决定状态栏、导航栏、输入法窗口等出现在哪些位置以及加不加布局参数。哪怕SystemUI没有创建导航栏View如果这里窗口策略认为导航栏窗口应该存在窗口管理器依然可能预留出“位置”或者在某些特性比如freeform窗口、多屏下把系统栏逻辑拉起来。我最终的方案是在PhoneWindowManager的navigationBarPosition或者windowForStatusBar相关方法里增加对自己定制系统属性的判断明确告诉窗口策略本设备导航栏状态栏窗口不可用不需要为此预留任何空间。核心思路参考// PhoneWindowManager.java 关键修改示意 private boolean isNavBarDisabled() { return android.os.SystemProperties.getBoolean(persist.sys.hidenav, false); } Override public int getNavigationBarPosition(int displayId, int displayWidth, int displayHeight, int displayRotation) { if (isNavBarDisabled()) { return NAV_BAR_POSITION_BOTTOM; // 或者在这里直接改变策略 } return super.getNavigationBarPosition(displayId, displayWidth, displayHeight, displayRotation); }这里要特别提醒一个坑不同版本的Android12代码结构有些微差异RK3588各厂商BSP合入情况也不一样改之前先在自己的源码环境里grep确认方法签名不要直接照搬网上的代码。比如我的代码里getNavigationBarPosition方法在RK官方的SDK里参数可能多了个NonNull DisplayPolicy编译不过就得自己调整。3.4 双保险属性的设计思路我把隐藏系统栏的控制拆成了两个属性persist.sys.hidenav和persist.sys.hidestatus。为什么拆开因为实际项目中这两种需求不是同时出现的。有的客户只要隐藏导航栏保留顶部状态栏显示时间有的客户要全屏两个都不要。拆开属性之后产品工程师通过修改build.prop或者adb shell setprop就能灵活配置不用每次改代码重新编译。属性控制还有一个额外的好处如果在产线调试阶段发现系统栏被压坏或者有异常可以直接adb shell setprop persist.sys.hidestatus false adb shell stop adb shell start不用重新烧固件。这在量产现场能救命因为很多时候固件已经烧录了不可能为了调试状态栏再重刷一遍系统。4. 实操记录在RK3588 Android12源码里一路改下去4.1 环境准备与代码定位首先确认你的环境能完整编译Android12系统镜像。RK3588平台多使用./build.sh或者./make.sh依赖device/rockchip/rk3588这个产品目录下的BoardConfig和device.mk。我这里以RK官方发布的Android12 SDK为例基线的BUILD_ID是SP1A.210812.016以前的RK3588较早版本代码路径大体一致。用以下命令先确认代码位置# 进入AOSP根目录 cd ~/rk3588_android12 # 查找NavigationBar grep -rn class NavigationBar packages/apps/SystemUI/src/com/android/systemui/navigationbar/ # 查找StatusBar grep -rn makeStatusBarView packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java # 查找PhoneWindowManager grep -rn navigationBarPosition frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java确认好文件路径后建议先自己编译一版原始代码烧进去确认开发板的系统栏行为是正常的方便后面定位。这一步很多人跳过最后出了问题分不清是代码问题还是环境问题排查成本反而更高。4.2 代码修改详解让系统栏在启动时就直接消失下面给出在RK3588 Android12上可以落地的修改组合。不保证每个BSP版本都一字不差核心思路和位置是对的遇到差异自己灵活处理。第一步修改NavigationBar.java拦截创建在NavigationBar.java的init方法头部增加属性判断。注意init方法的调用时机在SystemUI启动早期如果在onFinishInflate之后才拦截View已经创建了效果就差很多。// packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java // init方法头部增加 import android.os.SystemProperties; private boolean isNavHidden() { return SystemProperties.getBoolean(persist.sys.hidenav, false); } Override public void init(NonNull Context context) { if (isNavHidden()) { Log.i(NavigationBar, skip init because persist.sys.hidenavtrue); return; } ... }第二步修改StatusBar.java跳过状态栏窗口添加找到StatusBar的makeStatusBarView和addStatusBarWindow相关逻辑。在addStatusBarWindow里增加拦截同时把mStatusBarWindowController的设置改成一个无操作版本避免后续收到setSystemUiVisibility回调又把状态栏拉出来。// packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java private boolean isStatusHidden() { return SystemProperties.getBoolean(persist.sys.hidestatus, false); } private void addStatusBarWindow() { if (isStatusHidden()) { Log.i(StatusBar, skip addStatusBarWindow because persist.sys.hidestatustrue); return; } ... }第三步修改PhoneWindowManager.java禁止系统栏窗口存在在PhoneWindowManager.java里搜索navigationBar的创建条件找到系统栏窗口的布局配置方法在窗口排列逻辑前设置标志位。Android12里NavigationBarFrame相关布局受WindowManagerPolicy的getWindowLayerFromTypeLw控制主要改动是在canShowNavigationBar或者isStatusBarKeyguard这类方法里加入属性判断。我这里以canShowNavigationBar为例// frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java Override public boolean canShowNavigationBar(int displayId) { if (SystemProperties.getBoolean(persist.sys.hidenav, false)) { return false; } return super.canShowNavigationBar(displayId); }第四步修改config.xml双保险在frameworks/base/core/res/res/values/config.xml里把config_showNavigationBar强制设为false同时把导航栏默认尺寸改为0。这个改动对老逻辑管用虽然Android12上有部分代码路径不走这里了但留着能防万一。!-- frameworks/base/core/res/res/values/config.xml -- bool nameconfig_showNavigationBarfalse/bool dimen namenavigation_bar_height0dp/dimen dimen namenavigation_bar_height_landscape0dp/dimen这里有个很重要的提示修改config.xml会影响所有系统应用如果某些特殊应用比如Launcher自己实现了虚拟按键画面可能会有问题。不过正常情况下我们这是彻底隐藏不会引入虚拟按键。4.3 编译烧录与验证方法代码改完之后只需要编译framework和SystemUI相关模块即可不用整个系统镜像全部重编。在AOSP根目录执行# 编译framework source build/envsetup.sh lunch rk3588-userdebug make framework -j$(nproc) # 编译SystemUI make SystemUI -j$(nproc) # 生成对应镜像 make systemimage -j$(nproc)如果只想快速验证SystemUI修改可以直接把SystemUI编译出的apk推到设备上然后重启SystemUI进程adb root adb remount adb push out/target/product/rk3588/system/system_ext/priv-app/SystemUIGoogle/SystemUIGoogle.apk /system_ext/priv-app/SystemUIGoogle/ adb shell stop adb shell start验证命令主要看系统栏是否真的不在窗口层级里# 查看当前window层级 adb shell dumpsys window windows | grep -E StatusBar|NavigationBar # 查看系统属性是否生效 adb shell getprop persist.sys.hidenav adb shell getprop persist.sys.hidestatus当整机烧录启动后你会发现主界面干干净净应用区域真正全屏了。滑动屏幕边缘、点击底部角落、按电源键再唤醒系统栏都不会再出现。这就是我们说的“彻底隐藏”。4.4 利用adb属性做动态调试的技巧开发阶段强烈建议保留属性开关方便你随时把系统栏调出来看效果。属性值修改后重启SystemUI即可生效adb shell setprop persist.sys.hidenav false adb shell setprop persist.sys.hidestatus false adb shell stop adb shell start还有一个调试技巧当SystemUI启动异常时可以先看日志adb shell logcat -s NavigationBar StatusBar PhoneWindowManager通过日志确认我们的属性判断是否生效、拦截分支是否走到了预期的代码。如果persist.sys.hidenav已经为true但日志里没打印“skip init”说明SystemUI的代码没编译进去或者进程没重启干净优先检查编译链路。5. 躲在细节里的坑状态栏残留、旋转屏幕、输入法冲突5.1 改完了还有残留多半是广播消息或系统回调很多时候SystemUI隐藏得挺好但某些应用拉起了一个“全屏Dialog”或者系统弹出了电量低、存储空间不足的警告界面底部又闪出系统栏。这种情况说明隐藏过程没拦截住WindowManager的addView请求有些窗口类型属于系统栏级别不需要SystemUI配合也会被创建。排查思路用adb shell dumpsys window windows | grep -E Window查看闪现窗口的包名和窗口类型再去对应的framework代码里增加判断。还有一个常见元凶是SystemAlertWindow某些权限弹窗、网络共享提醒会带出系统栏需要在PhoneWindowManager里把这些弹窗的布局参数中的系统栏可见性也一并处理。5.2 横竖屏切换后系统栏怎么又冒出来了横竖屏切换是最容易触发系统栏闪现的场景。原因是旋转时DisplayContent会重建窗口配置SystemUI的onConfigurationChanged被调用部分代码路径会重新给NavigationBar或者StatusBar发送系统栏可见性的改变命令。我们的属性判断在初始化时生效了但配置更新时会绕过初始化逻辑导致系统栏短暂出现再隐藏。解决办法是在Configuration变化回调里再增加一次同样的属性判断保证每次配置变化后都强制隐藏。这个坑非常隐蔽我一开始没处理结果客户做老化测试时发现旋转屏幕必现系统栏闪烁后来加了这个处理才解决。5.3 输入法弹出顶起布局导航栏却不见了输入法与隐藏系统栏的冲突也比较常见。默认情况下输入法弹出时系统会重新布局导航栏所在区域如果导航栏被彻底隐藏输入法的adjustResize逻辑可能会把布局顶得很奇怪。实测下来隐藏导航栏后输入法弹出时应用底部会有一段空白区域这是系统为“可能出现的导航栏”预留的空间实际上导航栏已经不存在了。这个问题要改InputMethodManager的窗口调整逻辑或者在应用层让主界面的Window.setSoftInputMode使用adjustNothing或者sLayoutInScreen。对于广告机这类不输入文本的场景建议直接在应用侧把输入法禁用省得引入一连串兼容问题。5.4 低概率闪回导航栏的终极保险定时器强制隐藏即便做了上述所有修改某些厂商BSP版本还是会在特定场景蓝牙配对弹窗、分屏模式、强制停用对话框下重新拉出系统栏。这也是我最后加“定时器强制隐藏”的原因在SystemUI的StatusBar中注册一个每500毫秒执行的Handler检查系统栏View状态如果发现可见且属性要求隐藏就立即重新隐藏。这个做法比较粗暴但极其有效适合量产阶段的兜底方案。定时器不要放在主线程密集执行用Handler加延迟循环或者Choreographer回调都可以5秒执行一次排查即可没有必要设在1秒以下避免无谓耗电。6. 另一条路不碰源码用Overlay机制隐藏系统栏6.1 RRORuntime Resource Overlay原理与适用场景如果你所在的团队没有完整源码环境或者产品只是做白牌定制、不打算深度维护RRO覆盖资源的方式是更合适的方案。RRO的核心思路是不修改系统源码而是在vendor/overlay目录下创建一个资源覆盖包运行时由系统加载覆盖替换掉目标资源的值。对于隐藏系统栏RRO能把config_showNavigationBar覆盖为false把导航栏高度覆盖为0dp某些场景下能达到隐藏效果。而且RRO包的开发不需要整编AOSP只需要用aapt2打一个最小的APK配合PRODUCT_PACKAGE_OVERLAYS变量即可。6.2 实际配置方法与局限在RK3588的Android12上RRO的方式确实有效但有两个硬伤第一它隐藏的是“系统配置中定义的导航栏”很多BSP版本的导航栏窗口创建不再完全依赖这个资源值应用全屏AIDL也会绕过它第二状态栏的图标、通知面板等资源不是单纯靠覆盖dimen就能消失的需要同时覆盖layout侵入性不低。我的建议是RRO适合快速出样机、给客户看效果的阶段使用正式量产尤其是有稳定性要求的商显设备还是回到源码修改更牢靠。RRO改了资源值但系统服务的行为逻辑没动后续一旦有窗口策略触发系统栏显示照样会出现。7. 常见问题与排查技巧实录这次把我在RK3588 Android12上排查过程中遇到的高频问题整理成一个速查表格方便你在现场快速定位。表格里每个问题都是我实际遇到过、并且验证过解决思路的。现象可能原因排查手段解决办法隐藏后底部仍可滑动拉出导航栏WindowManager策略未同步修改dumpsys window windows修改PhoneWindowManager的canShowNavigationBar开机先闪一下导航栏再消失SystemUI初始化后属性判断执行晚logcat观察NavigationBar初始化把属性判断提前到init方法最前面竖屏正常横屏导航栏回弹配置变更未重新判断属性横屏时logcat抓取配置变化onConfigurationChanged重新隐藏隐藏后应用底部空白系统为导航栏预留了底部Inset检查系统setSystemUiVisibility应用setDecorFitsSystemWindows(false)SystemUI崩溃重启隐藏时仍有代码引用已隐藏viewlogcat看堆栈同步修改回调注册逻辑属性设置true但无效果SystemUI没重启getprop看属性值adb shell stop adb shell start状态栏隐藏但通知栏下拉还在命令队列未拦截dumpsys statusbar修改CommandQueue的setSystemUiVisibility处理再说一个偏门但很实用的技巧RK3588的HDMI输出和多屏显示场景下隐藏系统栏不止要看主屏副屏的DisplayPolicy也需要一并处理。如果只改了主屏外接显示器或副屏打开时系统栏会在副屏上重新出现等发现这个问题的时候往往是客户已经插了HDMI设备了临时去改框架层会很被动。所以多屏产品在开发之初就要把副屏窗口策略纳入隐藏范围。8. 一点个人体会做了这么多RK3588商显项目隐藏系统栏这件事看似不起眼实际上是直接影响交付质量的关键环节。系统栏不隐藏客户验收直接打回系统栏隐藏不彻底老化测试三天就暴露问题。我踩过最深的坑就是过于相信“一行代码解决”的捷径最终老老实实把SystemUI、PhoneWindowManager、配置资源三条链路全部对齐才算真正稳定下来。如果你只打算做一次验证RRO或者沉浸模式可以应付但如果你是做产品、做交付请务必投入时间去改源码并且保留属性开关方便后续调试。最后再分享一个习惯每次改完系统栏相关代码我都会用一个脚本自动压测——随机切换横竖屏、随机唤醒休眠、随机滑动屏幕边缘跑上两小时没有系统栏回弹才敢把固件放给客户。这个习惯帮我挡掉过至少三次现场事故强烈建议你也试试。