Android暗黑模式适配

前言

最近这两年,用户关于支持暗黑模式的呼声越来越高。而友商也基本都上了暗黑模式,于是老板也要求我们年前得上,可是工作量太大了,200多个页面,一个人整,实在是没法年前发,就拖到了年后上。

成果

1.网页和文章兼容安卓10及其以上,由网页前端处理;
2.原生页面设置浅色和深色都有效。但是跟随系统的话,由于安卓系统10以下没有设置暗黑模式的入口,所以设置跟随系统后一直是浅色。
3.由于App由原生 View 和 Compose View 实现的,所以两套 UI 都得实现暗黑模式;
4.在处理 UI 基础 library 和业务 library 实现了暗黑模式的同时,需要处理没有暗黑模式要求的 App ,直接设置为浅色模式即可

遇到的问题:

  1. 无法兼容 android 10 以下的系统
  2. 修改暗黑模式后,部分颜色没有更新
  3. 修改暗黑模式后,跟随系统部分手机不生效
  4. 系统暗黑模式修改后,无论当前处理什么模式,所有 Activity 都会 recreate

问题1:无法兼容 android 10 以下的系统

1.1 第三方库 Android-skin-support
开始之前也在网上查过相关的实现,类似功能就是换肤。
但是,我们做的是暗黑模式,包括:浅色,深色,跟随系统。
其中浅色和深色比较好实现,但是跟随系统的话,换肤功能就不方便实现了。用的最多的就是 Android-skin-support 这个库,实现起来也不容易,当然自己实现也挺麻烦。
未处理的 issue 接近三位数了,最后一次维护是3年前,慎用。
插件换肤,我们除了需要在国内各大应用市场上架外,还需要在 google play 上架,皮肤颜色相关的资源打包成一个 .apk 文件放在住工程内就不符合 google play 的上架规范。
1.2 我们的用户手机系统低于 android 10 的用户占比不多,小公司也那么多人手去做这种投入产出比较低的事情
1.3 我们的 App 有传统 View 和 Compose View 的实现,接入可能产生其他问题

问题2:修改暗黑模式后,部分颜色没有更新

2.1 所有 View 相关的页面,包括 View 和 Compose View 混合的页面的 Activity 必须继承自 AppCompatActivity。单纯的 Compose 页面没有这个要求。
2.2 除非特殊页面,比如分享页外,所有地方的颜色都不能直接硬编码。所有颜色均使用 xml 中定义的两套颜色,或使用 Compose 调色板中指定的主题颜色。
2.3 View 设置颜色不能直接使用 application 的 Context 作为上下文去获取颜色,需使用 Activity 的 Context 去获取上下问。

问题3: 修改暗黑模式后,跟随系统部分手机不生效

这个手查了很久才解决的问题,起初发现是在 debug 版本和 beta 版本之间的差异问题,在 debug 版本中不存在这个问题。最后发现是由于 debug 版本中关闭了多语言的功能,为了提升编译速度,禁止了多语言相关的实现。
最后发现是在对多语言处理的时候,导致了 Configuration 中的 uiMode 出现了问题。然后解决办法也就很清晰了:

override fun attachBaseContext(context: Context?) {
        super.attachBaseContext(InjectUtils.updateLocalConfig(context))
    }
    
override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        InjectUtils.onConfigChange(newConfig) // 再次矫正 UI_MODE
    }
// InjectUtils.kt
fun updateLocalConfig(context: Context?): Context? {
        return context?.let {
            val languageContext = setLocalLanguage(it)
            setLocalDarkMode(languageContext)
        }
    }
private fun setLocalDarkMode(context: Context?): Context? {
        val res = context?.resources
        val config = Configuration(res?.configuration)
        config.uiMode = DarkModeUtils.getCurrentUIMode(config.uiMode)
        return context?.createConfigurationContext(config)
    }
// DarkModeUtils.kt 根据当前模式还原正确的 UI_MODE 参数
fun getCurrentUIMode(uiMode: Int) = when (getCurrentMode()) {
        AppearanceMode.DAY -> Configuration.UI_MODE_NIGHT_NO
        AppearanceMode.NIGHT -> Configuration.UI_MODE_NIGHT_YES
        else -> Configuration.UI_MODE_NIGHT_UNDEFINED
    }.let {
        uiMode and Configuration.UI_MODE_NIGHT_MASK.inv() or it
    }

问题四:系统切换暗黑模式导致 AppCompatActivity 重启

暂时无法解决,看起来是系统处理的。
4.1 对于重要的页面,进行数据保存: onSaveInstanceState
4.2 说服老板

Compose 才是暗黑模式的最佳实现

其实 Compose 页面是最好实现暗黑模式的,切换暗黑模式也不会导致 Activity 重启,上面问题四说的是 View 页面的 AppCompatActivity 会重启。
而且 Compose 不仅仅能实现浅色和黑色两套主题,N 套都没问题,新增主题就可以了。
反关 View 实现的页面就不行了,因为只有 color.xml 和 color-night.xml 两套,或许还有其他方式,不过也不简单吧。
然后问题了下 iOS 的同事,说 iOS 切换暗黑模式页面也不会 recreate ,算了,还是 View 页面的设计不合理吧。

你可能感兴趣的:(安卓开发,ui,android,darkmode)