第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 配置。

实现流程

  1. 添加依赖:在项目级 build.gradle 中引入 DataStore 库。
  2. 创建 DataStore 实例:通常使用委托属性 preferencesDataStore 在顶层或单例中定义。
  3. 写入数据:调用 context.dataStore.edit { ... },在 lambda 中使用 toPreferences() 或直接写入键值。
  4. 读取数据:通过 dataStore.data 获取 Flow,使用 map 转换出所需类型(如 Int)。
  5. 观察变化:在 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+)。
  • 在 edit lambda 中设置断点,观察 Preferences 对象结构。
  • 检查文件是否存在:/data/data/<package>/files/datastore/settings_prefs.preferences_pb(默认二进制文件)。

部署

  1. 多进程安全:如果应用有多个进程,需为每个进程创建不同 DataStore 实例(文件名不同),否则会抛出 FileAlreadyExistsException。
  2. 混淆规则:-keepclassmembers class * { @androidx.datastore.preferences.core.IntPreferencesKey *; }(如果使用 ProGuard)。
  3. 迁移方案:从 SharedPreferences 迁移时可使用 SharedPreferencesMigration,但需谨慎处理默认值。

常见错误

错误原因解决方案
FileAlreadyExistsException同一 DataStore 名称被多个进程创建为不同进程起不同的文件名
CoroutineScope is not active在 edit 未完成的协程中切出作用域使用 viewModelScope 或 lifecycleScope 管理协程
读取总是返回默认值未正确调用 putInt 或键名不一致检查 intPreferencesKey 的名称是否拼写一致
UI 不更新未在 UI 层 collect 或使用了 Flow 的 terminal operation确保在 lifecycleScope 中调用 collect

性能或稳定性优化

  • 减少不必要的写入:在 edit lambda 中只修改实际变化的键,避免对大量键重复赋值。
  • 使用 distinctUntilChanged:在 Flow 链上添加 .distinctUntilChanged(),只有当值真正变化时才通知观察者,减少 View 刷新。
  • 避免在主线程调用 edit:始终在 IO 调度器执行(DataStore 内部默认使用 Dispatchers.IO,但显式指定更安全)。
  • 压缩与备份:DataStore 文件为 protobuf 二进制格式,体积小,无需额外压缩;定期清理过期数据可提升读取速度。
Preferences DataStore 读写与 Flow 观察流程图 应用层(协程) DataStore 实例 edit { 写入键值 } (挂起函数,IO 线程) preferences_pb 文件 (原子写入) data: Flow<Preferences> map 转换出 Int UI 层 collect 写入完成 文件变化 关键点:写入通过 edit 挂起函数 -> 原子更新本地文件 -> 文件变化自动发射新 Flow 值 推荐在 ViewModel 中使用 viewModelScope 管理协程,UI 层只负责 collect