RK3588 Android 12禁用Monet动态取色:从原理到固化方案
做 RK3588 Android 12 固件定制的时候经常有客户提一个需求壁纸怎么换系统主题色都别跟着变。这个需求背后的元凶就是 Android 12 默认开启的 Monet——Google 所谓的 Material You 动态取色。它会在壁纸更换后自动从图片里提取主色然后把 SystemUI、通知栏、设置页、部分第三方应用的配色全部换一遍。对消费机来说这是卖点对行业终端、广告机、一体机来说就是灾难。这篇文章就围绕怎么禁用 Monet 展开先讲原理再给调试阶段的快速关闭命令最后给量产固件的编译期方案覆盖 RK3588 Android 12 以及大多数 Android 12 AOSP 分支不做脱离实际的“理论分析”每一步都是能直接跑的命令和能落地的改动。1. Monet 到底在哪一层起作用1.1 从壁纸到主题色一条完整的动态变色链路Monet 不是一个独立应用也不是一个普通开关。它本质上是“取色 调包”的组合系统检测到壁纸变化从壁纸 Bitmap 里提取主要颜色把颜色交给 SystemUI 的取色引擎再由引擎生成一套动态主题资源最后通过 Android 的资源覆盖机制把 SystemUI、Launcher、通知栏等模块的配色全部替换掉。要禁用这个功能不能只靠“换一张纯色壁纸”这种土办法因为只要取色链路还活着无论壁纸内容是什么颜色都会被抽象成主题色只是观感上不明显而已。整套链路可以拆成四步壁纸变化触发WallpaperManager发现壁纸文件被替换向 SystemUI 的ThemeOverlayController发送通知。取色阶段SystemUI 拿到壁纸 Bitmap交给ColorExtractor等取色算法从图片里提取主色、辅色、强调色。生成主题阶段取色结果被封装成ColorScheme生成一组系统主题 overlay 的资源值。应用阶段OverlayManagerService 根据生成的颜色值启用对应的运行时资源覆盖包RROSystemUI 会立刻重绘。理解了这条链路你就明白为什么禁用 Monet 有多个下手点可以从源头不取色可以不让ThemeOverlayController响应壁纸变化也可以直接在 overlay 管理层面禁用相关资源包。选择哪种方式取决于你的目标是“调试机快速验证”还是“量产固件彻底关死”。这里顺带提一句做行业定制时Android 12 默认的权限授予策略也会被客户反复问到比如 Android 12 上 targetSdk 31 的应用默认权限预授权方式变了很多厂商需要在开机时做批量授权。这类默认行为和 Monet 一样都属于 AOSP 默认策略“不太适合行业终端”的典型例子经常被放在同一张需求单上处理。Monet 相对容易被忽略因为不仔细看根本不会发现颜色在变但一旦客户换了壁纸整个 UI 色系全乱问题立刻就暴露了。1.2 为什么不能用“杀掉 SystemUI”或“锁死壁纸”凑合最原始的做法是监听壁纸变化一旦发生变化就强制把壁纸替换回去。这在实际项目中往往不靠谱一是客户可能就是想换壁纸只是不希望界面变色二是WallpaperManager和系统取色逻辑之间是异步的你强制替换壁纸的时机很难卡准经常出现“替换成功但是取色已经跑完”的情况。另一个野路子是直接杀掉 SystemUI让系统重启 UI这更不可取因为 SystemUI 会自动恢复并且重新执行壁纸取色逻辑。真正的解决办法还是回到机制层面把动态取色的“信号源”切断或者把取色结果“应用不上”。这样即使客户换壁纸系统也只会当壁纸换了UI 配色纹丝不动。后面的方案都是围绕这两条思路展开的。2. 先跑通再固化调试机上关掉 Monet2.1 第一步找到当前生效的 Monet 相关 Overlay拿到一台 Android 12 设备建议先别急着改代码用 adb 在系统层把动态取色关掉验证这条路走不走得通。第一步是看看当前系统里到底有哪些 overlay 和 Monet 相关。adb shell cmd overlay list | grep -i monet adb shell cmd overlay list | grep -i color输出大致长这样不同 BSP 的包名会有差异以实际输出为准[ ] com.android.internal.systemui.monet [ ] com.android.systemui.monet [ ] com.android.internal.systemui.color方括号里的状态表示 overlay 是否被启用。其中带monet字样的包就是动态取色的核心资源包只要这些包处于启用状态主题色就会跟随壁纸变化。cmd overlay list输出的包名是最可靠的参考不同厂商的 Android 12 分支里这些包名的后缀可能不完全一致建议做项目时先跑一遍命令把包名记录下来后续所有操作都以这份实际列表为准。2.2 第二步清掉主题覆盖包并禁用 Overlay找到相关包名后执行下面三条命令adb shell settings put secure theme_customization_overlay_packages adb shell cmd overlay disable com.android.internal.systemui.monet adb shell cmd overlay disable com.android.systemui.monet adb shell pkill -f com.android.systemui第一条命令的作用是清空主题定制 overlay 的注册列表。Android 12 的 ThemeManager 会读取Settings.Secure里的theme_customization_overlay_packages字段这个字段里保存着当前风格下应该启用的 overlay 包名置为空串后SystemUI 就不知道要加载哪些动态取色资源包取色结果自然无法落地。后两条命令是把刚才查到的 Monet 相关 overlay 全部禁用这是兜底操作防止清空字段后系统缓存里仍然残留旧状态。最后pkill -f com.android.systemui是为了让 SystemUI 彻底重启重新读取设置。这里有一个容易被忽略的点如果cmd overlay disable提示overlay is a static overlay说明这个 overlay 是静态启用的不能在运行时直接禁用。遇到这种情况不用慌继续执行第一条清空字段的命令再重启 SystemUI一般也能挡住动态取色如果要彻底解决就得走第 3 章的编译期方案。2.3 第三步验证关闭效果执行完命令后先看状态adb shell cmd overlay list | grep -i monet adb shell settings get secure theme_customization_overlay_packages正常的预期是monet 相关 overlay 状态从[x]启用变成[ ]禁用theme_customization_overlay_packages返回null或空串。然后换一张色彩强烈的壁纸观察设置页、通知栏、音量条的颜色是否变化。如果颜色保持不变说明运行时禁用成功。如果发现颜色还是会变优先检查两个东西一是系统里是否还有别的 Monet overlay 没被禁用比如第三方定制的 overlay二是部分 GMS 应用如 Google 提供的壁纸应用会主动从壁纸取色跟系统 Monet 无关这种情况需要单独处理。这些干扰项我放到后面第 4 章专门说。3. 量产固件编译期把 Monet 关死在系统里3.1 用 RRO 覆盖动态取色开关调试机验证通过后就要把改动固化到固件里。最省事的方式是给系统打一个运行时资源覆盖包RRO把控制动态取色的开关直接覆盖成关闭状态。在 AOSP 分支里framework 资源里通常有一个布尔值控制是否允许动态颜色常见字段名是config_allowDynamicColors不同分支的具体字段可能会有出入需要先搜索确认。在设备目录下新建一个 overlay 目录例如device/rockchip/rk3588/overlay/frameworks/base/core/res/res/values/config.xml内容如下resources bool nameconfig_allowDynamicColorsfalse/bool /resources然后在这个 overlay 目录下添加编译配置如果项目用 Android.bp可以这样写runtime_resource_overlay { name: MyFrameworkOverlay, theme: android, product_specific: true, }再把 overlay 加进产品打包列表例如在 device.mk 里加PRODUCT_PACKAGES MyFrameworkOverlay这样编译出来的固件就会携带一个名为MyFrameworkOverlay的 overlay在开机时覆盖 framework 里的动态取色开关。SystemUI 读取到false后不会生成动态主题资源Monet 相当于被系统废弃了。这种方式的好处是不改源码只加资源和配置风险最低后续要恢复也容易。需要注意字段名一定要以当前源码为准如果编译后没效果先确认 overlay 有没有真正编译进系统再看字段名对不对别盲目相信网上的固定写法。3.2 直接断掉 SystemUI 的取色调度如果 RRO 覆盖不生效或者你希望更彻底地干掉取色链路就得动 SystemUI 源码。核心文件是ThemeOverlayController.java这个类负责监听壁纸变化、调度取色、应用颜色 overlay。定位到start()方法找到壁纸变化回调相关的注册直接禁用。以 AOSP 12 常见代码为例修改思路如下Override public void start() { // 禁用 Monet不再注册壁纸变化监听 // mContext.registerReceiver(mWallpaperChangedReceiver, // new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); } Override public void onWallpaperChanged() { return; // 直接忽略壁纸更新阻止取色调度 }不同版本的代码结构会有差异建议先搜索WallpaperChanged和onWallpaperChanged关键字找到对应位置再决定注释哪些行。这个方案最彻底因为连取色信号都没有了不管后续怎么配置 overlay都不会有动态变色发生。但改 SystemUI 源码需要留意副作用如果同一段代码里还承载了深色模式切换、用户手动选择主题色等功能粗暴注释会把正常功能也干掉。建议在改之前先把这个类的完整逻辑读一遍弄清楚哪些方法只服务于 Monet哪些方法被多个功能共用只注释掉 Monet 专属的部分。3.3 改掉 SettingsProvider 的默认值第 2 章运行时方案里我们其实是手工把theme_customization_overlay_packages的当前值置空了。这个设置如果不改默认值设备恢复出厂设置后还是会回到原始状态因为 SettingsProvider 的 defaults 配置里会写入默认 overlay 列表。所以在量产固化时还需要找到frameworks/base/packages/SettingsProvider/res/values/defaults.xml在文件里搜索theme_customization关键字找到对应的默认值配置项把默认值改成空串string namedef_theme_customization_overlay_packages/string这样设备无论怎么恢复出厂设置动态取色的 overlay 列表始终是空的Monet 不会复活。这个改动建议和第 3.1 节的 RRO 覆盖一起做一个管运行态一个管出厂默认值双管齐下稳定性更高。注意如果你的系统是 RK3588 这类 BSPSettingsProvider 可能已经被厂商定制过默认值字段的位置不一定和 AOSP 完全一致最快的定位方式是全局搜索theme_customization_overlay_packages字符串找到之后顺着资源索引去 defaults 文件里改。3.4 清理系统里默认携带的 Monet Overlay 包最后一步是把系统里预置的 Monet overlay 包本身移除。这些包通常以com.android.internal.systemui.monet、com.android.systemui.monet之类的名字出现在frameworks/base/packages/SystemUI或vendor目录下。如果它们不存在即使 RRO 开关失效、Settings 默认值被清空系统也没有资源可应用颜色自然回退到默认 Material 色。移除方式有两种一是从PRODUCT_PACKAGES列表里去掉对应包二是直接用 overlay 覆盖掉这些包的资源。第一种方式更彻底但需要确认这些包没有被其他模块依赖建议在PRODUCT_PACKAGES里搜索一下包名确认不影响到其他功能再移除。如果不想移除也可以在编译脚本里把 overlay 的默认启用状态改成 false。这个要看具体包实现有的是在 AndroidManifest 里声明android:overlaytrue和priority有的是在资源里控制config_enableMonet之类的开关找到对应资源改掉即可。整体原则是能不动源码就不动源码资源层能解决的就别碰 Java 代码。4. 改完后的坑我基本都踩过一遍4.1 刚关掉又变色Settings 被系统服务还原最常见的坑是调试机上执行settings put后当时确实不变色了但重启设备或者过一段时间颜色又回来了。原因很简单theme_customization_overlay_packages这个值会被 SystemUI 或者其他系统服务周期性写回你手动置空很可能在一段时间后就被系统兜底逻辑恢复了。解决办法有两个方向一是别依赖单一的 Settings 修改配合cmd overlay disable一起做至少保证当前 overlay 是禁用状态即使 Settings 被还原没有 overlay 可用也变不了色二是把改动放在开机启动阶段在 init 脚本或开机广播里再次执行settings put确保每次开机都先把值置空。我在实际项目里测过单独靠settings put在 RK3588 上重启后大概率失效必须配合编译期改动才能稳定。所以快速验证和量产固化要分开看不要觉得调试机跑通了固件就一定能复现。4.2 Launcher 图标颜色仍然会随壁纸变禁用 Monet 之后还有一个容易遗漏的地方是 Launcher 的动态图标。AOSP 的 Launcher3 在 Android 12 上会直接读取壁纸取色结果用于生成动态图标背景这个逻辑不一定走系统 overlay可能是 Launcher 自己内置的取色算法。所以经常出现“SystemUI 已经不变色了Launcher 的图标背景还在跟着壁纸变”的尴尬情况。处理方式是在 Launcher 的资源或代码里关闭动态图标。Launcher3 里一般会有FLAG_USE_DYNAMIC_COLOR或类似的宏搜索dynamic关键字把开关关掉让图标固定使用默认取色或品牌色。如果用的是 RK3588 BSP 自带的第三方 Launcher那就得看 Launcher 自己的设置项通常在壁纸设置或主题设置里可以关闭跟随壁纸变色实在不行只能改 Launcher 源码。另外还要提醒一点部分第三方应用会在 targetSdk 31 之后主动调用WallpaperManager的颜色提取 API自己做主题换肤这些跟系统 Monet 完全无关。遇到这种应用只能靠应用的设置项关闭系统层面没法一刀切。4.3 编译不通过或 Overlay 不生效的排查编译期方案最常遇到的问题是改了资源、加了 overlay但烧进设备后一点反应都没有。排查顺序我一般是这样先确认 overlay 有没有真正打包进固件。烧机后执行adb shell cmd overlay list | grep MyFrameworkOverlay如果列表里找不到说明产品打包配置有问题检查PRODUCT_PACKAGES有没有写对名字Android.bp 里的name和 product 变量是否一致。再确认 overlay 是否处于启用状态。如果是静态 overlay默认是启用的如果是动态 overlay可能还需要用cmd overlay enable开启。接着确认资源字段名是否正确。config_allowDynamicColors在不同 AOSP 分支里不一定存在如果你覆盖了一个不存在的资源编译时可能不会报错但运行时也不会有效果。用grep -r allowDynamicColors frameworks/base/core/res/确认字段存在。最后确认 target package 是否写对。RRO 里的theme: android表示覆盖 framework 资源如果目标包写成了 SystemUI需要改成theme: com.android.systemui否则资源覆盖不到对应模块。我遇到过最奇葩的情况是 overlay 编译进去了、也启用了但 SystemUI 有自己的一套动态取色缓存需要重启两次才生效。所以排查的时候不要着急先看 overlay 状态再看资源字段最后考虑缓存问题按顺序来能省很多时间。4.4 禁用后深色模式异常还有一个比较容易踩的坑是禁用了 Monet 后深色/浅色模式的切换逻辑跟着乱了。原因是动态取色资源里不仅包含主色调还包含深色模式下的一组衍生色你把这条链路切断后系统如果对深色模式的颜色资源依赖比较重就会出现“切到深色模式后部分文字看不清”或“背景色发灰”的现象。遇到这种情况不要想着重新开启 Monet那是拆东墙补西墙。正确做法是检查 SystemUI 和 framework 的深色模式资源看缺少哪个颜色项在 overlay 里手工补一个固定的深色配色。最简单的方式是复用默认的system_accent_color系列资源把深色模式下缺失的颜色项指过去保证整体 UI 颜色统一但不动态变化。这个问题的排查思路也不复杂先对比深色模式下显示异常的颜色名称再去资源目录里搜索对应的 color 资源看看哪一项被动态取色覆盖了然后显式写死一个默认值即可。最后分享一点我处理这类需求的排序先想清楚是验证还是出货验证就 adb 命令快上快下出货就把编译期改动做完整别指望靠几行settings put应付过去。Monet 虽然只是 Android 12 的一个默认行为但在行业终端上它和权限预授权一样都是会被客户拿放大镜看的功能点。我这边踩完这些坑后后续项目基本固件一出厂就是关掉的再也没被退回过。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →