ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android 12禁用Monet动态取色:RK3588商显定制完整指南

Android 12禁用Monet动态取色:RK3588商显定制完整指南 去年我们在 RK3588 平台的一台商显一体机上做 Android 12 定制碰到了一个特别有意思的投诉客户整套 VI 是深蓝我们也按需求把默认壁纸换成了深蓝品牌壁纸结果设备刚开机下拉通知栏、设置开关、音量条一下全变成了壁纸里的某个橙色。客户在现场截图说“系统不稳定颜色会自己变”。一查根本不是不稳定是 Android 12 默认开启的 Monet 动态取色在“抢班夺权”。这个事其实很有代表性。Android 12 把 Material You 动态主题作为卖点但对做 B 端设备定制的人来说它经常是个需要第一时间按掉的“功能”。这篇文章就把我们当时从排查、禁用、到处理残留问题的完整过程写出来主要针对 RK3588 平台 Android 12 SDK思路对高通、MTK 的 Android 12/13 同样适用。1. 为什么行业定制设备会想关掉 Monet 动态取色1.1 真实场景一台 RK3588 商显一体机的“变色”投诉商显一体机的使用场景大家应该能想象到会议室预约牌、电梯厅信息屏、门店数字标牌、工业产线看板。这类设备的核心诉求是稳定、品牌统一界面颜色必须和企业的 VI 体系保持一致。客户定的规矩是开机界面、主屏、设置菜单、状态栏全部统一用一种品牌蓝。问题就出在壁纸上。Android 12 的 Monet 会从当前壁纸里提取主色、辅色、强调色再生成整套主题色板。只要壁纸不是纯色系统的开关、按钮、高亮颜色就会跟着变。你设了一张带橙色光斑的产品宣传壁纸那系统强调色就是橙色你设了一张绿色办公场景壁纸系统强调色就变绿。客户不管什么叫 Material You他只看到“颜色会自己变”这就是一个实打实的稳定性问题。更麻烦的是技术支持成本。项目交付之后现场人员如果发现系统颜色变了第一反应是回滚固件、重刷系统。你不可能派个人去现场解释“这是动态主题的 Feature”。所以在设备定制里关掉 Monet 不是“要不要”的问题而是“在哪个环节关”的问题。1.2 Monet 是普通手机用户的爽点却是设备定制里的不可控变量Google 设计 Monet 的出发点非常明确让每个用户通过壁纸获得属于自己的个性化系统配色把系统 UI 的颜色控制权交给用户。手机用户换张壁纸看到整套 UI 颜色变化体验是新鲜的。但这套逻辑在 B 端设备上完全不成立。B 端设备的用户不是设备的“主人”是“使用者”。壁纸可能是项目方统一推送的运营素材也可能是维修人员随手换上去的测试图。如果系统主题色会随着壁纸内容随机变化那几个月后同一批设备放在一起界面颜色可能五花八门。对于品牌展示类设备这种情况是不可接受的。还有一个容易被忽视的点动态取色会给系统带来额外的计算和渲染开销。壁纸变化后SystemUI 要重新提取颜色、重新生成色板、通知 Launcher 更新工作区、通知 Settings 刷新控件颜色。虽然单次开销不大但商显设备经常是 7x24 小时运行积累下来的资源占用、潜在的取色时机问题对于追求长期稳定性的项目都是风险。所以很多 RK3588 方案商在交付行业固件时会直接把 Monet 关成默认值。1.3 需求边界去掉的是“动态”不是所有主题能力需要先统一一下概念禁用 Monet 不等于把系统打回 Android 11 那种“只有一种蓝色点缀”的老样子。Android 12 的视觉语言仍然是 Material Design 风格系统默认会有一套 baseline 颜色资源禁用动态取色后整个系统会固定使用这套默认颜色。你可以通过资源覆盖把默认颜色改成客户的品牌色让 UI 既保持 Android 12 的圆角、卡片、间距等设计风格又不再因为壁纸而变化。所以我们的目标很明确系统所有 UI 的强调色、辅助色、背景色在设备整个生命周期内保持不变且可以由项目方通过资源文件统一配置。搞清楚这条边界后面动起手来就不会误伤别的东西。2. 先搞清楚 Monet 的取色链路再动手2.1 壁纸到 ColorSchemeWallpaperColors 是怎么一步步变成 UI 颜色的网上搜“Android 12 Monet 禁用”大部分答案就是让你改一个 bool。但如果不理解 Monet 的完整链路改完 bool 往往发现还有一堆地方在变色然后开始怀疑是不是没改对。所以先花两分钟把链路捋一遍。Monet 的核心链路分四步。第一步壁纸设置。壁纸位图写入 WallpaperManager 之后系统会从位图里提取一组颜色信息形成的对象叫WallpaperColors里面包含 primaryColor、secondaryColor、tertiaryColor 三类主要色值以及一些用于对比度计算的辅助信息。壁纸颜色提取算法本身不复杂本质上是对位图像素做聚类和量化选出最代表性的几个颜色。第二步颜色分发。SystemUI 里的MonetController监听 WallpaperColors 变化。它内部持有一个 ColorDisplayManager 的引用注册了取色回调。壁纸一变取色回调触发MonetController 就会把新的颜色交给 ColorScheme。第三步生成色板。ColorScheme拿到种子色seed color之后会按 Material Design 的 tonal palette 规则把一个颜色扩展成 5 个色调组合每个组合下面再细分 0 到 100 的明度梯度。最终生成的是 system_accent1_500、system_accent2_500、system_accent3_500、system_neutral1_500、system_neutral2_500 这一整组动态色板。第四步应用到 UI。SystemUI、Settings、Launcher3 以及三方应用有的直接引用android:color/system_accent1_500这类资源有的通过DynamicColors.applyToActivityIfAvailable()把动态色应用到控件上。系统资源值一层层传递用户看到的就是壁纸里的某个颜色变成了整个 UI 的主色调。2.2 主题色最终进了哪些 UI影响面有多大禁用之前必须盘点清楚动态色会影响多少组件否则后面就会出现“SystemUI 不变色了但 Launcher 还在变”这种半吊子状态。第一层是 SystemUI 内部。下拉快捷设置区域的开关背景、通知卡片的时间文字、音量面板的滑块、锁屏时钟的部分元素、截屏浮窗这些都属于 SystemUI 渲染使用动态色资源或 ColorScheme 生成的颜色。第二层是系统应用。Settings 里的开关、滑块、浮出按钮、图标背景色大量用了动态色。比如“壁纸和风格”页面里的取色球、存储空间色块这些都是动态色的直接体现。如果只关 SystemUI 不关 Settings用户进设置依然看得到变色。第三层是 Launcher3。Launcher3 会单独订阅 WallpaperColors用它调整工作区背景的深浅、预测应用图标背后的色块、以及页面指示器的颜色。注意Launcher3 这条链路不经过 SystemUI 的 MonetController它有自己的一套监听逻辑这也是很多人关了 config_monet 之后发现桌面还在变色的原因。第四层是三方应用。应用可以通过android:themestyle/Theme.Material3.DynamicColors.DeviceDefault.DayNight或者直接读取system_accent1_*资源来使用动态色。只要 framework 层的动态色资源值更新了所有按标准方式取色的应用都会同步变化。如果只是把个别 UI 组件的颜色固定死那修不完因为取色源头是同一个。正确的做法是在“取色”这个源头切断或者让取色结果恒定不变。2.3 关键控制点config_monet 与 MonetController 的关系搞清楚了链路禁用思路就清楚了要么让 WallpaperColors 变化不影响系统资源要么让取色回调根本不被注册。AOSP 在 SystemUI 里提供了一个现成的总开关res/values/config.xml里的config_monetbool。这个值的默认状态是 true它控制 MonetController 是否启用整套监听与取色流程。当这个值为 false 时SystemUI 不会注册 WallpaperColors 回调ColorScheme 不会拿壁纸种子色去更新色板系统 UI 就会稳定使用默认的 baseline 动态色资源。注意这里的“动态色资源”本身还存在但它的值不再被运行时更新了。也就是说从框架层看Monet 不是“删掉”而是“停摆”。这个设计非常适合定制系统因为我们可以用一个很小的资源覆盖把它关掉完全不动 SystemUI 的代码。下一节我会对比几条不同的禁用路线。3. 禁用 Monet 的三条路线对比3.1 路线一RRO 资源覆盖最轻量、最适合批量出机所谓 RRORuntime Resource Overlay你可以理解为给系统资源打一层“贴纸”。我们不修改任何系统源码只写一个很小的 overlay APK声明目标包名是com.android.systemui然后把config_monet这个 bool 覆盖成 false。系统启动时资源框架会优先读取 overlay 中的值于是 SystemUI 的取色流程直接被关闭。这条路线的优势非常明显不动源码不增加平台分支的维护压力适配性强同一个 overlay 可以从 Android 12 沿用到 Android 13/14前提是 config_monet 这个名字还存在于 SystemUI出问题可以快速回滚把 overlay 从固件里摘掉重编即可。我们在 RK3588 项目里最终选的就是这条路。理由很简单方案商手里的平台要同时维护多个客户项目直接在 SystemUI 源码里改后面同步 AOSP 高分代码时几乎必然冲突而一个独立的 overlay 工程就是一个模块目录跟其他客户项目完全隔离。3.2 路线二SystemUI 源码裁剪适合深度定制 ROM 分支如果你的团队本来就深度定制了 SystemUI比如改了状态栏布局、通知中心样式、快捷开关排布那么直接改 MonetController 的代码也不失为一个自然的选择。常见的源码改法是让MonetController.isMonetAvailable()直接返回 false或者在其父类注入链路上不装配这一个 SystemUI 服务。这样编译器直接移除相关逻辑效果上比资源覆盖更彻底因为 Class 可能压根不初始化。但这条路线的代价也实打实每次同步 AOSP 大版本Monet 相关代码的 git 冲突都是必然的因为这一块的改动频率还不低。而且如果只是关一个开关为了这个引入一整批源码级别的 patch后面排查问题时反而多了一个嫌疑点。我的建议是除非你的 SystemUI 已经改了两位数以上的文件否则优先走 overlay。3.3 路线三保留机制但固定 seed 色兼顾“Material You 风格”与品牌色还有一种需求形态客户喜欢 Android 12 这种比较柔和的色彩体系但受不了壁纸换来换去颜色跟着变。这种情况不需要完全禁用 Monet只需要把 ColorScheme 的 seed color 固定成一个品牌色比如深蓝#0F4C81。实现上SystemUI 的 ColorScheme 通常有一个updateColorScheme(seed: Int)方法。我们只需要在 MonetController 启动时执行一次updateColorScheme(FIXED_SEED)之后不管壁纸怎么变都不再触发新种子色更新。最终效果是系统 UI 仍然有全套 tonal palette只是整套颜色永远围绕品牌色生成既保留了 Material You 的动态配色质感又保证了品牌一致性。这个方案在代码里大概长这样class MonetController Inject constructor( private val mContext: Context, private val mColorScheme: ColorScheme ) : SystemUI() { override fun start() { // 定制固定品牌取色种子关闭壁纸驱动 mColorScheme.updateColorScheme(FIXED_SEED) // 不注册壁纸颜色变化回调 } companion object { private const val FIXED_SEED 0xFF0F4C81.toInt() } }具体注释和返回值要根据你们 Android 12 分支的实际代码调整但思路是通用的。3.4 三条路线怎么选一张表供参考我把三种方案的关键差异整理了一下对比维度RRO 资源覆盖SystemUI 源码裁剪固定 seed 色是否随壁纸变色不随壁纸变不随壁纸变不随壁纸变主题色调风格系统默认 baseline 色系统默认 baseline 色品牌固定色 tonal palette改动范围独立 overlay 模块SystemUI 源码 patchSystemUI 源码 patchAOSP 升级维护成本低高高适合场景多客户批量出机深度定制 ROM 分支既要品牌统一又要动态质感大部分项目我推荐第一行后面两种情况属于特殊需求。下面第四节就以 RRO 资源覆盖为主把完整实操过程走一遍。4. 实操RK3588 Android12 SDK 禁用 Monet 的完整过程4.1 创建 overlay 工程目录与配置文件我们在 RK3588 SDK 的device/rockchip/rk3588/下面新建了一个模块目录名字就叫disable_monet_overlay。目录结构如下device/rockchip/rk3588/disable_monet_overlay/ ├── Android.bp ├── AndroidManifest.xml └── res/values/config.xml先说AndroidManifest.xml。overlay 应用本身不需要任何 java 代码所以hasCode设为 false。最关键的是 manifest 里要声明overlay标签targetPackage必须精确写成com.android.systemui写错任何一个字母都不会生效。manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.disablemonet.overlay application android:hasCodefalse / overlay android:targetPackagecom.android.systemui android:isStatictrue android:priority1 / /manifestisStatictrue的意思是这是一个静态 overlay系统启动时会自动启用不需要每次开机后手动执行 enable。priority用来声明多个 overlay 同时命中同一资源时的优先级我们这里只有一个填 1 就行。然后是res/values/config.xml内容非常短resources bool nameconfig_monetfalse/bool /resources这就是整个禁用的核心。资源名字必须和 SystemUI 源码里的完全一致它本身声明在packages/SystemUI/res/values/config.xml中。你可以在自己的 SDK 里打开这个文件搜索一下确认当前版本的名字大概率还是config_monet。Android.bp里我们直接用android_app模块类型因为它对编译 RRO 最直观证书用 platform这样它有系统权限级别android_app { name: DisableMonetOverlay, sdk_version: current, platform_apis: true, certificate: platform, manifest: AndroidManifest.xml, resource_dirs: [res], }如果你的平台编译系统比较老不支持platform_apis: true这种写法可以改成sdk_version: system_current。具体以 SDK 里现有应用的 Android.bp 写法为准。把这个模块加入你产品的PRODUCT_PACKAGES比如PRODUCT_PACKAGES DisableMonetOverlay这样编译固件时它就会被编进去。4.2 编译、烧录与验证RK3588 平台的编译指令一般是这样source build/envsetup.sh lunch rk3588_userdebug mmm DisableMonetOverlay编出来的 APK 在out/target/product/rk3588/对应的 overlay 输出目录下。烧录整机固件后验证步骤不能省adb shell cmd overlay list在输出里找DisableMonetOverlay确认后面的状态是[x]。如果显示[ ]说明没有自动启用执行adb shell cmd overlay enable --user 0 com.disablemonet.overlay然后重启验证效果。最快的验证方式是换壁纸先把系统壁纸设置成一张红色为主的图等桌面颜色稳定后再换成一张绿色为主的图。如果 Monet 已经关闭下拉通知栏、设置里的强调色不应该有任何变化。如果还在变大概率是下面第五节的 Launcher 或 Settings 残留问题。4.3 源码级强制关闭MonetController 修改示例如果你决定走源码级方案我给出一个相对保守的修改示例。Android 12 里 SystemUI 的 MonetController 是通过依赖注入装配的它继承自SystemUI核心入口是start()。我们不直接删类避免牵连其他注入点只是在启动时让它早退。class MonetController Inject constructor( private val mContext: Context, private val mExecutor: Executor, private val mColorDisplayManager: ColorDisplayManager, private val mColorScheme: ColorScheme ) : SystemUI() { override fun start() { // 定制化修改读取 overlay 开关关闭时不再注册取色回调 if (!mContext.resources.getBoolean(R.bool.config_monet)) { return } // ... 原有逻辑 } }注意这里做了一个很关键的小设计源码里照样读config_monet这样同一个代码分支既能支持 RRO 关闭也能支持默认开启。以后想恢复把 overlay 摘掉重新编译即可不需要再改代码。如果你确认某个平台永远不开 Monet也可以直接把start()里的逻辑整体注释掉效果更彻底。4.4 把默认强调色改成品牌色一步到位禁用之后系统会回退到 AOSP 默认的 baseline 动态色。Google 默认的 baseline 色是偏紫蓝的 Material 色对这个 RK3588 商显项目来说还是不符合客户 VI。所以我还需要把系统的默认强调色覆盖成品牌深蓝。这里要特别注意system_accent1_500这一系列资源都属于 android framework 包不是 SystemUI 包。换句话说刚才那个DisableMonetOverlay管不到它需要再写一个 target 为 android 的 overlay或者直接在你的 framework overlay 工程里追加。我们新建了一个brand_color_overlay结构类似差别在 manifestmanifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.brandcolor.overlay application android:hasCodefalse / overlay android:targetPackageandroid android:isStatictrue android:priority100 / /manifest在它的res/values/colors.xml里覆盖需要替换的几个核心色值resources color namesystem_accent1_0#0F4C81/color color namesystem_accent1_500#0F4C81/color color namesystem_accent1_700#0A355A/color /resources实际项目里我们会把所有 0-1000 梯度的 system_accent1 都替换成品牌色的不同明度版本这样无论 UI 组件引用哪个明度档位都能在品牌色系内变化不会出现开关是深蓝、进度条却是浅蓝这种割裂感。颜色梯度的计算可以用 Material Color Utilities 工具也可以让设计师直接给一组色阶。5. 顺带处理 Launcher 与设置入口避免“半禁用”状态5.1 Launcher3 仍在读壁纸颜色怎么办前面说过Launcher3 有独立的壁纸颜色监听逻辑不归 SystemUI 的 MonetController 管。这就是很多项目关掉 config_monet 后发现设置和通知栏都不变色了但桌面图标背景色、页面指示器还在变的根本原因。处理方式要看你们的 SDK 用的是哪种 Launcher。如果是 AOSP Launcher3核心是找到读写WallpaperColors的地方。通常 Launcher 里有类似LauncherWallpaperChangedListener的回调接口里面有一个onWallpaperColorsChanged方法。我们在重写方法里直接 no-op并且不再把取到的颜色传递给 UIOverride public void onWallpaperColorsChanged(WallpaperColors colors, int which) { // 定制化修改禁用 Launcher 跟随壁纸取色 }如果 SDK 用的是三方 Launcher比如 Rootless Pixel Launcher 或第三方桌面就先看它的壁纸取色开关在哪个配置里。绝大部分三方 Launcher 都有“壁纸取色”或“动态图标着色”的开关直接关掉即可。实在找不到开关再考虑在它读取 WallpaperManager 的公共入口处做 Hook但维护成本会高一些。5.2 设置入口把“壁纸和风格”做成不可见禁用 Monet 后Settings 里的“壁纸和风格”页面会残留一个尴尬的入口用户点进去能看到取色预览但调了半天颜色没反应。对行业设备来说这种无效入口最好直接藏掉。AOSP 的 Settings 里“壁纸和风格”入口通常由WallpaperAndStylePreferenceController控制controller 的isAvailable()决定这个列表项是否显示。最简单的改法是在 Settings overlay 里覆盖对应的可见性配置或者直接改 controller 让其返回 falseif (Features.MONET_ENABLED) { return true; } else { return false; }很多定制 ROM 里已经有类似config_show_wallpaper_and_style的配置开关优先找有没有现成配置没有就改 controller。如果你不想动 Settings 源码还有一个更粗暴的折中把设置里的“壁纸”应用入口置灰。不过这个需要单独处理壁纸应用的 Intent-filter工作量比想象中多一般不推荐。5.3 第三方应用仍在使用动态色的治理思路有些行业 APP 是外包开发的开发方图省事直接在代码里调用 Material Components 的DynamicColors.applyToActivityIfAvailable()让整个应用自动跟随系统动态色。如果平台层的动态色资源已经固定这类应用在大多数设备上是不会变色的因为system_accent1_*的值已经恒定。但有一种边界情况要留意如果目标应用的依赖库版本较新它可能自己走了一套独立的取色逻辑而不经过系统资源层。这种情况在定制 ROM 里治理起来成本很高因为你不能去改每个三方应用的代码。行业内通常的做法是在项目需求文档里就约束好行业设备上预装的应用不得启用动态取色。如果发现有应用绕过系统强制取色优先联系应用方出一版关闭动态主题的包而不是在系统侧死磕。6. 实测中的几种异常与排查记录6.1 overlay 编进固件但启动后没有生效我们第一次把 DisableMonetOverlay 加进 RK3588 固件时烧录完成后发现系统 UI 还是随壁纸变色。第一反应是 config_monet 这个名字不对但去 SystemUI 源码里确认过名字没问题。后来adb shell cmd overlay list一看状态是[ ]说明静态 overlay 没有被 OMS 正确自动启用。原因出在编译阶段我们的 Android.bp 用了android_app类型编出来的 APK 被系统当成普通应用安装到了/system/app而不是/system/overlay所以 OMS 没有在启动阶段把它扫描为 RRO。解决方法有两个一种是把 APK 的安装路径改到/system/overlay让 OMS 识别另一种是编译后用adb shell cmd overlay enable --user 0手动启用。量产固件里建议走前者确保开机即生效不要依赖手动 enable。如果你用的平台有完善的 rro 集成目录也可以直接参考 SDK 里已有的 overlay 模块写法通常它们已经解决了安装路径问题照抄结构是最稳的。6.2 首次开机时系统 UI 闪了一下错误配色禁用之后还有一个很微妙的时序问题设备开机过程中壁纸服务可能还没有完全就绪WallpaperColors 可能读取失败或者读到一张默认壁纸的颜色。在极端情况下SystemUI 的某一个组件会先按临时取色渲染一帧然后再刷成固定色视觉上就是开机瞬间闪了一下别的颜色。这个问题在商用设备上不多见因为开机动画本身会覆盖大部分启动过程。但如果客户对开机流程敏感录屏发现了这个一闪而过的瞬间处理思路有两个一是把默认壁纸改成纯黑或纯品牌色减少取色结果的随机性二是在 MonetController 启动早期禁止任何 ColorScheme 更新直到固件里预设的品牌 seed 被加载。6.3 关闭 Monet 后状态栏图标仍然会“变色”有个现象容易让新手误判为 Monet 没关干净状态栏的时钟、信号、Wi-Fi 图标在浅色壁纸下是深色的在深色壁纸下会变成白色。这个是 Android 系统的“明暗对比”逻辑不是 Monet 的取色逻辑。它的目的是保证状态栏图标在任何壁纸背景下都有足够的可见度。这个逻辑由 WindowManager 根据壁纸亮度计算决定和主题色板无关。如果你希望状态栏图标颜色也完全固定那属于更深层的系统 UI 定制需要改 SystemUI 的StatusBar里关于mLightBarController和图标颜色的判断逻辑。一般情况下不建议动因为纯白状态栏图标在浅色壁纸下会直接看不清属于负优化。6.4 排查工具与调试小技巧最后分享几条调试时非常实用的命令。看 overlay 当前状态adb shell cmd overlay list这条命令输出里如果看到目标 overlay 前面的括号是[x]表示已启用[ ]表示未启用。强制启用某个 overlayadb shell cmd overlay enable --user 0 com.disablemonet.overlay看某个 overlay 实际覆盖了哪些资源adb shell cmd overlay dump --user 0 com.disablemonet.overlay排查 framework 动态色资源当前的值adb shell dumpsys resources | grep system_accent1_500如果这里的值还是随壁纸在变说明取色链路没有被拦截如果值一直不变但 UI 还在变色那问题就出在应用自身而不是系统资源。另一条经验是修改 SystemUI 相关代码后不需要整编固件可以先单独编 SystemUI 和 overlay 再 push能省下大量调试时间。RK3588 单编 SystemUI 的指令一般是mmm packages/SystemUI adb root adb remount adb push out/target/product/rk3588/system/systemui/SystemUIGoogle.apk /system/priv-app/SystemUIGoogle/ adb reboot具体路径以你的 SDK 实际输出为准但流程是通用的。我个人在这套方案上踩过不少坑最大的体会是不要一上来就动 SystemUI 源码。先花二十分钟做一个 overlay 验证能不能解决问题比直接沉进去改代码要高效得多。而且把“禁用 Monet”和“替换品牌色”拆成两个独立 overlay后续哪个客户要、哪个客户不要产品配置里一行就能切换不用维护两个固件分支。后面如果你们遇到类似问题建议先按这个思路走一遍大概率能少走一半弯路。
返回列表