DataStore:设置的一致更新流 · AndroidX 源码指南
AAndroidX 源码指南
数据层
数据层 · androidx.room3

DataStore:设置的一致更新流Room3 3.0.0-rc01

Preferences 与 Proto 两种 DataStore、edit/updateData 原子写、迁移与单例约束。

最后更新 2026-08-01

Preferences DataStore:最简起步

无预定义 schema 的 typed-key 存储,适合少量设置项:

import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

private val Context.settings by preferencesDataStore("settings")
private val DarkMode = booleanPreferencesKey("dark_mode")

val darkMode: Flow<Boolean> = context.settings.data
    .map { preferences -> preferences[DarkMode] ?: false }

suspend fun setDarkMode(enabled: Boolean) {
    context.settings.edit { preferences ->
        preferences[DarkMode] = enabled
    }
}

支持的 key 类型:booleanPreferencesKeyintPreferencesKeylongPreferencesKeyfloatPreferencesKeystringPreferencesKeystringSetPreferencesKey

单例约束(重要)

源码的 preferencesDataStore delegate 明确要求顶层只创建一次PreferenceDataStoreDelegate.android.kt):

// ❌ 错误:每次调用都新建,破坏协调假设
fun someScreen(context: Context) {
    val settings by context.preferencesDataStore("settings")
}

// ✅ 正确:顶层单例,跨组件共享
private val Context.settings by preferencesDataStore("settings")

delegate 内部用 applicationContext 解析文件路径,避免 Activity 泄漏;同一文件重复创建实例会破坏读改写的一致性。

原子读改写

edit {} 的 transform 接收当前 Preferences 快照,返回新 Preferences——整个 transform 在单进程内串行执行:

// ✅ 原子:读当前值 → 计算 → 写回,内部加锁
context.settings.edit { prefs ->
    val count = prefs[CounterKey] ?: 0
    prefs[CounterKey] = count + 1
}

// ❌ 非原子:先读 Flow 再盲写,两个协程并发会丢更新
val current = settings.data.first()[CounterKey] ?: 0
settings.edit { it[CounterKey] = current + 1 }

updateData {} 是 core API 的等价物,Preferences 层通常用 edit

Proto DataStore:需要结构时

Preferences 的 key 是运行时拼的,无法表达嵌套结构。需要 schema 时用 Proto DataStore

// proto 定义(.proto 文件)
// message UserSettings { bool dark_mode = 1; int32 language = 2; }

// 自定义 Serializer
val UserSettingsSerializer = object : Serializer<UserSettings> {
    override val defaultValue: UserSettings = UserSettings.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): UserSettings =
        UserSettings.parseFrom(input)

    override suspend fun writeTo(t: UserSettings, output: OutputStream) {
        t.writeTo(output)
    }
}

private val Context.userSettings by dataStore(
    fileName = "user_settings.pb",
    serializer = UserSettingsSerializer,
)

选择规则:

需求选择
少量键值设置Preferences
结构化数据、类型安全、需要版本演进Proto
大量列表/关联数据Room(不是 DataStore)

迁移

从 SharedPreferences 迁移(一次性、幂等):

val Context.settings by preferencesDataStore(
    fileName = "settings",
    produceMigrations = { context ->
        listOf(
            SharedPreferencesMigration(
                context,
                "legacy_shared_prefs_name"
            )
        )
    }
)

自定义迁移在首次数据访问前执行;迁移可能被重试,逻辑必须幂等(重复执行结果一致)。

常见陷阱

  • 重复实例:同文件多个 DataStore 实例互相覆盖写入。
  • 迁移幂等:迁移代码不要假设只跑一次。
  • 损坏处理器:只处理反序列化损坏(返回恢复策略),不要吞掉所有 I/O 异常。
  • 不是数据库:大数据、查询、关联关系用 Room。
  • 主线程data 是冷 Flow,读取在 IO;不要在 UI 直接 .first() 阻塞。

复制即用

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

private val Context.settings by preferencesDataStore("settings")

object SettingsRepository {
    private val themeKey = preferencesKey<String>("theme")

    fun theme(context: Context): Flow<String> =
        context.settings.data.map { it[themeKey] ?: "system" }

    suspend fun setTheme(context: Context, value: String) {
        context.settings.edit { it[themeKey] = value }
    }
}

要点

  • Preferences 是 typed-key 集合;Proto 是 schema 化存储;两者都不是数据库。
  • delegate 顶层单例 + applicationContext;同文件只创建一个实例。
  • edit/updateData 原子读改写;迁移幂等;损坏处理器职责单一。
  • 设置少用 Preferences,结构化数据用 Proto,大量数据用 Room。

相关页面