第3章 Preferences DataStore基础读写:创建DataStore实例、putInt/getInt、使用Flow观察值变化
Preferences DataStore 是 Jetpack DataStore 的轻量级实现,专用于保存键值对数据。本章将带领你从零搭建一个 Preferences DataStore 实例,并通过 putInt / getInt 完成基础读写操作,同时利用 Kotlin Flow 永久观察数据的实时变化。本章内容是后续所有 DataStore 实战的基石。
原理
Preferences DataStore 底层基于 异步文件存储 与 事务式写入 实现。每次调用 edit 方法时,DataStore 会在后台原子性地更新本地文件(通常是 datastore_prefs 文件),并保证数据一致性和线程安全。读取时通过 data 属性暴露一个 Flow<Preferences>,每次文件内容变化自动发射新值,从而实现响应式观察。
数据存储位置:默认文件位于 app 内部存储目录
app_data_files/<文件名>,可通过 File.createTempFile 或 PreferenceDataStoreFile 配置。
实现流程
- 添加依赖:在项目级
build.gradle中引入 DataStore 库。 - 创建 DataStore 实例:通常使用委托属性
preferencesDataStore在顶层或单例中定义。 - 写入数据:调用
context.dataStore.edit { ... },在 lambda 中使用toPreferences()或直接写入键值。 - 读取数据:通过
dataStore.data获取Flow,使用map转换出所需类型(如Int)。 - 观察变化:在 UI 层或 ViewModel 中
collect该 Flow。
代码 / API 示例
以下是完整的 Kotlin 实现,包含实例创建、写入、读取和观察。
// 1. 添加依赖(版本以最新为准)
// implementation("androidx.datastore:datastore-preferences:1.1.1")
// 2. 创建 DataStore 实例(通常放在顶层扩展属性中)
val Context.settingsDataStore by preferencesDataStore(
name = "settings_prefs"
)
// 3. 定义键对象(避免魔法字符串)
private val KEY_COUNTER = intPreferencesKey("counter")
// 4. 写入数据:putInt
suspend fun saveCounter(context: Context, value: Int) {
context.settingsDataStore.edit { preferences ->
preferences[KEY_COUNTER] = value
}
}
// 5. 读取数据:getInt (返回 Flow<Int>)
val counterFlow: Flow<Int> = context.settingsDataStore.data
.map { preferences ->
preferences[KEY_COUNTER] ?: 0 // 默认值
}
// 6. 在 Activity / Fragment 中观察变化
lifecycleScope.launch {
counterFlow.collect { counter ->
textView.text = "当前值: $counter"
}
}
⚠ 注意:
edit 是挂起函数,必须在协程作用域内调用;data 属性返回的 Flow 在配置更改后会重新发射最新值,不需手动刷新。
调试
- 启用 DataStore 日志(Debug 环境):在
build.gradle中添加android { buildTypes { debug { minifyEnabled false ext.enableCrashlytics = false } } } - 使用 Android Studio 的 App Inspection → Database Inspector 查看 DataStore 文件内容(需运行在 API 26+)。
- 在
editlambda 中设置断点,观察Preferences对象结构。 - 检查文件是否存在:
/data/data/<package>/files/datastore/settings_prefs.preferences_pb(默认二进制文件)。
部署
- 多进程安全:如果应用有多个进程,需为每个进程创建不同 DataStore 实例(文件名不同),否则会抛出
FileAlreadyExistsException。 - 混淆规则:
-keepclassmembers class * { @androidx.datastore.preferences.core.IntPreferencesKey *; }(如果使用 ProGuard)。 - 迁移方案:从 SharedPreferences 迁移时可使用
SharedPreferencesMigration,但需谨慎处理默认值。
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
FileAlreadyExistsException | 同一 DataStore 名称被多个进程创建 | 为不同进程起不同的文件名 |
CoroutineScope is not active | 在 edit 未完成的协程中切出作用域 | 使用 viewModelScope 或 lifecycleScope 管理协程 |
| 读取总是返回默认值 | 未正确调用 putInt 或键名不一致 | 检查 intPreferencesKey 的名称是否拼写一致 |
| UI 不更新 | 未在 UI 层 collect 或使用了 Flow 的 terminal operation | 确保在 lifecycleScope 中调用 collect |
性能或稳定性优化
- 减少不必要的写入:在
editlambda 中只修改实际变化的键,避免对大量键重复赋值。 - 使用
distinctUntilChanged:在Flow链上添加.distinctUntilChanged(),只有当值真正变化时才通知观察者,减少 View 刷新。 - 避免在主线程调用
edit:始终在 IO 调度器执行(DataStore 内部默认使用Dispatchers.IO,但显式指定更安全)。 - 压缩与备份:DataStore 文件为 protobuf 二进制格式,体积小,无需额外压缩;定期清理过期数据可提升读取速度。