数据层
数据层 · 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 类型:booleanPreferencesKey、intPreferencesKey、longPreferencesKey、floatPreferencesKey、stringPreferencesKey、stringSetPreferencesKey。
单例约束(重要)
源码的 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。