You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 09:53:19