在 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 主题生命周期解剖
关键事实链:
AppCompatDelegate.setDefaultNightMode()是静态全局设置,仅影响后续创建的 Activity 实例recreate()触发的是「销毁 → 新构造 → onCreate → onStart」完整生命周期,但 不重置 AppCompatDelegate 的内部 mode 缓存- 系统在 Configuration 变更时会重建 Activity,但
Configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK的值由系统推导,而非直接继承前次 Delegate 状态 - 若未在
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 项
- ✅ 所有 Activity 继承自统一 BaseActivity,且其
attachBaseContext()被正确 override - ✅
values-night/下存在完整colors.xml、themes.xml、attrs.xml覆盖 - ✅ 自定义 View 的构造函数中使用
context.theme.obtainStyledAttributes()替代硬编码颜色 - ✅ 使用
AppCompatDelegate.MODE_NIGHT_NO / YES / FOLLOW_SYSTEM / UNSPECIFIED显式控制,禁用隐式 fallback - ✅ 在
onConfigurationChanged()中调用delegate.applyDayNight()并刷新关键 View - ✅ 使用
uiMode配置限定符测试:android:configChanges="uiMode" - ✅ Jetpack Compose 用户需额外检查
MaterialTheme是否包裹于DynamicColorProvider
七、避坑指南:5 年以上开发者仍常踩的 3 类反模式
- 反模式①:在
onResume()中调用setDayNight()—— 此时 DecorView 已绘制完成,部分控件(如 Toolbar)无法响应 - 反模式②:将夜间模式状态存在
ViewModel中 —— ViewModel 生命周期长于 Activity,recreate() 后 ViewModel 未被清除,但 theme 已失效 - 反模式③:依赖
resources.configuration.uiMode判断当前模式 —— 该值反映系统配置,而非 App 当前生效的 Delegate mode
本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报