使用AppCompatDelegate.setDefaultNightMode时生命周期回调顺序异常求助
调整主题切换时Activity生命周期回调顺序的解决方案
问题分析
当通过代码调用AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO/YES)切换主题时,AppCompat框架默认会优先触发栈顶Activity的生命周期回调,导致栈顶的onResumed先于底层Activity执行,和你期望的顺序相反。而跟随系统主题切换时仅栈顶Activity收到回调是系统默认行为,这部分无需修改。
实现方案:自定义主题切换协调器
通过自定义全局协调器控制Activity的重绘顺序,先处理底层Activity,最后处理栈顶Activity,确保栈顶的onResumed最后执行。
1. 维护Activity栈(在Application中实现)
通过ActivityLifecycleCallbacks跟踪所有存活的Activity,确保能准确获取栈顶页面:
class MyApp : Application() { private val activityStack = mutableListOf<Activity>() override fun onCreate() { super.onCreate() registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { activityStack.add(activity) } override fun onActivityDestroyed(activity: Activity) { activityStack.remove(activity) } // 其余生命周期方法空实现 override fun onActivityStarted(activity: Activity) {} override fun onActivityResumed(activity: Activity) {} override fun onActivityPaused(activity: Activity) {} override fun onActivityStopped(activity: Activity) {} override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} }) } // 获取当前栈顶Activity fun getTopActivity(): Activity? = activityStack.lastOrNull() }
2. 实现主题切换协调器
创建单例协调器,负责按顺序调度Activity的重绘:
object ThemeChangeCoordinator { private val pendingActivities = mutableListOf<WeakReference<Activity>>() private var isProcessing = false // 注册需要处理主题切换的Activity fun registerActivityForPostThemeChange(activity: Activity) { if (!isProcessing) { pendingActivities.add(WeakReference(activity)) } } // 按顺序处理Activity:先底层,后栈顶 fun processPendingActivities() { isProcessing = true val topActivity = (AppGlobals.getApplication() as MyApp).getTopActivity() // 先处理非栈顶Activity的重绘 pendingActivities.filter { it.get() != null && it.get() != topActivity } .forEach { ref -> ref.get()?.let { it.recreate() } } // 最后处理栈顶Activity topActivity?.recreate() pendingActivities.clear() isProcessing = false } }
3. 基类Activity中监听主题变化
在所有Activity的基类中,检测主题变化并注册到协调器:
open class BaseActivity : AppCompatActivity() { override fun onApplyThemeResource(theme: Resources.Theme, resid: Int, first: Boolean) { super.onApplyThemeResource(theme, resid, first) // 检测当前主题与目标主题是否不一致 val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK val targetNightMode = when (AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO else -> currentNightMode } if (currentNightMode != targetNightMode) { ThemeChangeCoordinator.registerActivityForPostThemeChange(this) } } }
4. 修改主题切换触发逻辑
调用setDefaultNightMode后,手动触发协调器按顺序处理:
// 自定义主题切换方法 fun switchAppTheme(mode: Int) { // 设置目标主题模式 AppCompatDelegate.setDefaultNightMode(mode) // 触发协调器按顺序重绘Activity ThemeChangeCoordinator.processPendingActivities() }
替代方案:延迟栈顶Activity的onResume
如果不想引入复杂的协调器,可在栈顶的ThemeSettingActivity中延迟执行onResume,确保底层Activity先完成生命周期回调:
class ThemeSettingActivity : BaseActivity() { private var isThemeSwitchPending = false override fun onResume() { if (isThemeSwitchPending) { // 延迟执行onResume,让底层Activity先完成回调 postDelayed({ super.onResume() isThemeSwitchPending = false }, 100) return } super.onResume() } override fun onApplyThemeResource(theme: Resources.Theme, resid: Int, first: Boolean) { super.onApplyThemeResource(theme, resid, first) val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK val targetNightMode = when (AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO else -> currentNightMode } isThemeSwitchPending = currentNightMode != targetNightMode } }
注意事项
- 使用
recreate()时,务必在Activity中通过onSaveInstanceState保存必要数据,避免数据丢失。 - 延迟时间可根据实际测试调整,确保底层Activity有足够时间完成生命周期回调。
- 若使用Fragment,需同步处理Fragment的生命周期回调,避免UI异常。
内容的提问来源于stack exchange,提问作者hulala NN
相关产品推荐
相关产品推荐

