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

从SharedPreferences迁移至PreferencesDataStore后,如何在Activity中实现主题(含深色模式)设置?

解决PreferencesDataStore适配主题设置的问题

我来一步步帮你解决这个迁移问题,先理清楚核心痛点,再给出符合Android架构规范的实现方案。

为什么你的现有方案不生效?

首先得搞明白:setTheme()必须在setContentView()或者任何视图初始化代码之前调用才能生效。你用LiveData observe回调时,回调是异步执行的,这时候Activity的视图可能已经初始化了,所以主题设置没效果;而runBlocking虽然能同步拿到值,但会阻塞主线程,大概率会触发ANR,绝对不能在UI线程这么用。

正确的适配方案

我们可以把主题相关的逻辑封装起来,用协程安全地获取配置并应用主题,同时处理后续的主题变化。

第一步:封装DataStore仓库层

先把所有偏好设置的读写统一放到一个SettingsDataStore类里,符合单一职责原则:

class SettingsDataStore(private val context: Context) {
    private val dataStore = context.createDataStore(name = "settings_preferences")

    // 深色模式开关Flow
    val darkModeFlow: Flow<Boolean> = dataStore.data
        .catch { exception ->
            if (exception is IOException) emit(emptyPreferences())
            else throw exception
        }
        .map { it[PreferencesKeys.DARK_MODE] ?: false }

    // 深色主题ID Flow
    val themeDarkFlow: Flow<Int> = dataStore.data
        .catch { exception ->
            if (exception is IOException) emit(emptyPreferences())
            else throw exception
        }
        .map { it[PreferencesKeys.THEME_DARK] ?: R.style.AppThemeDark }

    // 浅色主题ID Flow
    val themeLightFlow: Flow<Int> = dataStore.data
        .catch { exception ->
            if (exception is IOException) emit(emptyPreferences())
            else throw exception
        }
        .map { it[PreferencesKeys.THEME_LIGHT] ?: R.style.AppThemeLight }

    // 保存配置的方法
    suspend fun saveDarkMode(isDark: Boolean) {
        dataStore.edit { it[PreferencesKeys.DARK_MODE] = isDark }
    }

    suspend fun saveThemeDark(themeId: Int) {
        dataStore.edit { it[PreferencesKeys.THEME_DARK] = themeId }
    }

    suspend fun saveThemeLight(themeId: Int) {
        dataStore.edit { it[PreferencesKeys.THEME_LIGHT] = themeId }
    }

    // 定义偏好键
    private object PreferencesKeys {
        val DARK_MODE = booleanPreferencesKey("darkMode")
        val THEME_DARK = intPreferencesKey("themeDark")
        val THEME_LIGHT = intPreferencesKey("themeLight")
    }
}

第二步:创建ThemeManager封装主题逻辑

单独抽离一个ThemeManager类来处理主题的计算、应用和更新逻辑,这样所有Activity都能复用:

class ThemeManager(private val settingsDataStore: SettingsDataStore) {
    // 组合Flow,直接返回当前应该使用的主题ID
    val currentThemeFlow: Flow<Int> = combine(
        settingsDataStore.darkModeFlow,
        settingsDataStore.themeDarkFlow,
        settingsDataStore.themeLightFlow
    ) { isDark, darkTheme, lightTheme ->
        if (isDark) darkTheme else lightTheme
    }

    // 在Activity初始化前应用主题
    suspend fun applyInitialTheme(activity: AppCompatActivity) {
        val themeId = currentThemeFlow.first()
        activity.setTheme(themeId)
    }

    // 主题变化时重启Activity生效(View系统下最稳妥的方式)
    fun onThemeChanged(activity: AppCompatActivity) {
        activity.recreate()
    }
}

第三步:在ViewModel中集成

把ThemeManager注入到ViewModel,让ViewModel处理业务逻辑:

class SettingsViewModel(private val themeManager: ThemeManager) : ViewModel() {
    val currentThemeFlow = themeManager.currentThemeFlow

    // 保存深色模式设置
    fun toggleDarkMode(isDark: Boolean) {
        viewModelScope.launch {
            themeManager.settingsDataStore.saveDarkMode(isDark)
        }
    }

    // 保存深色主题
    fun saveDarkTheme(themeId: Int) {
        viewModelScope.launch {
            themeManager.settingsDataStore.saveThemeDark(themeId)
        }
    }

    // 保存浅色主题
    fun saveLightTheme(themeId: Int) {
        viewModelScope.launch {
            themeManager.settingsDataStore.saveThemeLight(themeId)
        }
    }
}

// ViewModel工厂类,用于注入ThemeManager
class SettingsViewModelFactory(private val themeManager: ThemeManager) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(SettingsViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return SettingsViewModel(themeManager) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

第四步:在Activity中使用

关键是在setContentView()之前通过协程获取主题并应用,然后观察主题变化:

class MainActivity : AppCompatActivity() {
    private lateinit var settingsViewModel: SettingsViewModel
    private lateinit var themeManager: ThemeManager

    override fun onCreate(savedInstanceState: Bundle?) {
        // 初始化依赖
        val settingsDataStore = SettingsDataStore(applicationContext)
        themeManager = ThemeManager(settingsDataStore)
        settingsViewModel = ViewModelProvider(
            this,
            SettingsViewModelFactory(themeManager)
        )[SettingsViewModel::class.java]

        // 先应用主题,再初始化视图
        lifecycleScope.launchWhenCreated {
            themeManager.applyInitialTheme(this@MainActivity)
            // 现在可以执行父类onCreate和视图初始化
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // 观察主题变化,变化时重启Activity
            settingsViewModel.currentThemeFlow.collectLatest {
                themeManager.onThemeChanged(this@MainActivity)
            }
        }
    }
}

关于你的其他问题

  1. 是否需要单独的类?
    非常有必要!像ThemeManager这样的类可以把主题相关的逻辑从Activity中抽离出来,既符合单一职责,又能在多个Activity中复用代码,维护起来更轻松。

  2. 符合Android架构规范的示例
    上面的代码完全遵循MVVM架构:

    • DataStore作为数据层,负责持久化存储
    • ViewModel作为业务逻辑层,处理配置的读写和主题状态的暴露
    • Activity作为UI层,只负责观察状态和渲染视图
    • 用协程和Flow处理异步数据,符合Jetpack的最佳实践

如果你的应用使用Hilt进行依赖注入,可以进一步优化SettingsDataStore和ThemeManager的创建方式,避免在Activity中手动实例化,让架构更整洁。

内容的提问来源于stack exchange,提问作者Markus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 15:12:28