徐中民 2026-02-07 18:00 采纳率: 98.7%
浏览 0
已采纳

recreate 后界面未恢复原昼夜模式,颜色状态丢失如何解决?

在 Android 开发中,Activity 重建(如屏幕旋转、配置变更触发 `recreate()`)后,界面未恢复原昼夜模式(Day/Night Theme),导致主题色、背景、文字等颜色状态丢失,是常见问题。根本原因在于:`AppCompatDelegate.setDefaultNightMode()` 的设置仅对新启动的 Activity 生效,而 `recreate()` 后系统未自动继承前次的 `night mode` 状态(尤其当未持久化或未在 `onConfigurationChanged()` 中同步时)。此外,若自定义 View 或 Theme 资源未正确适配 `values-night`,或 `Activity` 未调用 `getDelegate().applyDayNight()`,也会加剧状态不一致。典型表现包括深色模式下显示浅色 UI、StatusBar 颜色错乱、Material3 动态配色失效等。该问题多见于未重写 `onConfigurationChanged()`、未在 `Application` 或 `BaseActivity` 中统一管理夜间模式状态,或错误依赖 `SharedPreferences` 读取时机过晚(如 `onCreate()` 之后才应用主题)所致。
  • 写回答

1条回答 默认 最新

  • 秋葵葵 2026-02-07 18:00
    关注
    ```html

    一、现象层:Activity 重建后昼夜模式“失忆”——典型 UI 异常表现

    • 屏幕旋转后,深色主题 Activity 突然回退为浅色 UI(文字变黑、背景变白)
    • StatusBar/NavigationBar 颜色与当前 Night Mode 不匹配(如夜间模式下 StatusBar 仍为浅灰)
    • Material3 dynamicColor 未生效,MaterialTheme.colorScheme 返回错误调色板
    • 自定义 View 中通过 Context.getColor(R.color.xxx) 获取的颜色始终来自 values/ 而非 values-night/
    • Fragment 内部状态正常,但宿主 Activity 主题色错乱,表明 Theme 应用时机或作用域异常

    二、机制层:为什么 recreate() 不继承 Night Mode?——Android 主题生命周期解剖

    关键事实链:

    1. AppCompatDelegate.setDefaultNightMode() 是静态全局设置,仅影响后续创建的 Activity 实例
    2. recreate() 触发的是「销毁 → 新构造 → onCreate → onStart」完整生命周期,但 不重置 AppCompatDelegate 的内部 mode 缓存
    3. 系统在 Configuration 变更时会重建 Activity,但 Configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK 的值由系统推导,而非直接继承前次 Delegate 状态
    4. 若未在 Application.onCreate()BaseActivity.attachBaseContext() 中强制同步持久化 mode,则新 Activity 默认 fallback 到 MODE_NIGHT_FOLLOW_SYSTEM

    三、根因层:四大断裂点导致状态漂移

    断裂环节技术表现高危场景
    ① 持久化缺失SharedPreferences 未在 attachBaseContext() 前读取;mode 状态随进程重启丢失用户手动切换后杀进程再启动
    ② 时序错位onCreate() 中才调用 setDefaultNightMode() —— 此时 DecorView 已按默认 theme 初始化BaseActivity 中主题逻辑写在 super.onCreate() 之后
    ③ 资源适配断链values-night/colors.xml 缺失,或自定义 attr 未在 night 下 override使用 ?attr/colorSurface 但 night theme 未重定义该 attr
    ④ Delegate 同步遗漏未显式调用 getDelegate().applyDayNight(),导致 runtime theme 未刷新动态切换 mode 后未触发 UI 重绘(尤其 Fragment 场景)

    四、解决方案层:生产级昼夜模式韧性架构

    // ✅ 正确姿势:在 BaseApplication.attachBaseContext() 中统一接管
    override fun attachBaseContext(base: Context) {
        val mode = getSavedNightModeFromSP(base) // 必须在此处读取!
        AppCompatDelegate.setDefaultNightMode(mode)
        super.attachBaseContext(base)
    }
    
    // ✅ BaseActivity.onCreate() 前置保障
    override fun onCreate(savedInstanceState: Bundle?) {
        // 关键:必须在 super.onCreate() 之前确保 Delegate 已配置
        if (savedInstanceState == null) {
            // 首次启动:从 SP 加载并设为 default
            AppCompatDelegate.setDefaultNightMode(getPersistedMode())
        }
        super.onCreate(savedInstanceState)
        // 立即应用,覆盖可能的系统 fallback
        delegate.applyDayNight()
    }

    五、进阶防御:Material3 动态配色 + 配置变更兜底

    graph TD A[onConfigurationChanged] --> B{isNightModeChanged?} B -->|Yes| C[updateDynamicColorScheme] B -->|No| D[skip] C --> E[delegate.applyDayNight()] E --> F[rebind Views via ViewBinding.invalidateAll()] F --> G[notify ViewModel of theme change]

    六、验证清单:上线前必检 7 项

    1. ✅ 所有 Activity 继承自统一 BaseActivity,且其 attachBaseContext() 被正确 override
    2. values-night/ 下存在完整 colors.xmlthemes.xmlattrs.xml 覆盖
    3. ✅ 自定义 View 的构造函数中使用 context.theme.obtainStyledAttributes() 替代硬编码颜色
    4. ✅ 使用 AppCompatDelegate.MODE_NIGHT_NO / YES / FOLLOW_SYSTEM / UNSPECIFIED 显式控制,禁用隐式 fallback
    5. ✅ 在 onConfigurationChanged() 中调用 delegate.applyDayNight() 并刷新关键 View
    6. ✅ 使用 uiMode 配置限定符测试:android:configChanges="uiMode"
    7. ✅ Jetpack Compose 用户需额外检查 MaterialTheme 是否包裹于 DynamicColorProvider

    七、避坑指南:5 年以上开发者仍常踩的 3 类反模式

    • 反模式①:在 onResume() 中调用 setDayNight() —— 此时 DecorView 已绘制完成,部分控件(如 Toolbar)无法响应
    • 反模式②:将夜间模式状态存在 ViewModel 中 —— ViewModel 生命周期长于 Activity,recreate() 后 ViewModel 未被清除,但 theme 已失效
    • 反模式③:依赖 resources.configuration.uiMode 判断当前模式 —— 该值反映系统配置,而非 App 当前生效的 Delegate mode
    ```
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 今天
  • 创建了问题 2月7日