最近在测试中发现使用了Android官方提供的暗黑模式方案会出现,切换日夜间模式时闪一帧黑色的问题,故研究一下
一、官方暗黑(深色)模式框架
1.说明
深色模式设置可以从三个层级设置,分别是系统层、Applcation层以及Activity层。底层的设置会覆盖上层的设置,例如系统设置了深色模式,但是Application设置了浅色模式,那么应用会显示浅色主题。
通过AppCompatDelegate.getDefaultNightMode()可以获取五种深色模式场景:
- MODE_NIGHT_AUTO_BATTERY 低电量模式自动开启深色模式
- MODE_NIGHT_FOLLOW_SYSTEM 跟随系统开启和关闭深色模式(默认)
- MODE_NIGHT_NO 强制使用
notnight资源,表示非深色模式 - MODE_NIGHT_YES 强制使用
night资源 - MODE_NIGHT_UNSPECIFIED 配合 setLocalNightMode(int) 使用,表示由Activity通过AppCompactActivity.getDelegate()来单独设置页面的深色模式,不设置全局模式
切换主题通过AppCompatDelegate.setDefaultNightMode(MODE)来实现
2. Force Dark
forceDark是Android提供的一键适配夜间模式方案,对于没有使用DayLight主题的应用,通过系统底层去更改颜色来实现夜间模式,
开启方式:
设置主题的android:forceDarkAllowed为true,则应用会随着系统的暗黑模式切换而变化
需要注意:
- 要强制深色模式生效必须开启硬件加速(默认开启)
- 主题设置的Force Dark仅对
Light的主题有效,对非Light的主题不管是设置android:forceDarkAllowed为true或者设置View.setForceDarkAllowed(true)都是无效的。 - 父节点设置了不支持Force Dark,那么子节点再设置支持Force Dark无效。例如主题设置了
android:forceDarkAllowed为false,则View设置View.setForceDarkAllowed(true)无效。同样的,如果View本身设置了支持Force Dark,但是其父layout设置了不支持,那么该View不会执行Force Dark
3. 重建应用
系统切换暗黑模式会触犯Activity的重建,如果不希望Activity重建,则可以指定 android:configChanges="uiMode",然后重写 onConfigurationChanged() 方法,自己来处理切换逻辑。
二、闪黑问题
经过线下测试:同样的Andorid11设备 32和64位处理器都会出现闪黑问题,所以跟arm架构无关
又试了下Android12和Android13正常,那recreate导致闪屏结论就是跟Android系统有关,Android11及以下的设备必定会出现
试了很多Google系列的 app效果也是如此
三、根本原因
其实从现象不难推测,这肯定又是google搞得bug
从issue上来看在20年年底已经修复,按时间线来看是合入了Android12的分支
issuetracker.google.com/issues/1327…
但是我反查了一圈Activity和ActivityThread中关于recreate调用栈的提交记录,并没有发现有20年左右的有关提交
猜测之前的方案应该是把view又重新附着在window上,导致漏出了底部的window颜色
四、解决方案
1. 切换暗黑模式实现:
抛弃系统的暗黑模式方案,自己实现更换主题功能,业内有以下方案:
3.系统暗黑模式
三种方案的对比:
| | 系统暗黑模式 | Android-Skin-Loader | Android-skin-suport |
|---|---|---|---|
| 最新有效更新时间 | 向后兼容 | 2017 | 2020 |
| 侵入性 | 较低 | 相对较高 | 相对较低 |
| 支持加载皮肤包 | ❌ | ✅ | ✅ |
| 首次接入成本 | 相对较低 | - | 较高 |
| Target版本 | 29+ | - | 30以上有兼容问题 |
2.设置uiMode,不自动重建
在AndroidManifest.xml中指定 android:configChanges="uiMode",然后重写 onConfigurationChanged() 方法
- 手动重启Activity
本身就没有动画可以设置,亲测有效
val intent = Intent(this, BottomActivity::class.java)
startActivity(intent)
overridePendingTransition(0,0)
finish()
- 更新界面所有背景色
因为一般暗色模式的设置界面都是没有数据请求的简单界面,所以可以直接改变Activity所有view的颜色,该方案比较简单可根据项目自行实现