DataStore 与 Flow 结合:Preferences DataStore、Proto DataStore、实时读取、类型安全
4.1 DataStore 概述与 Flow 原生集成
DataStore 是 Jetpack 官方推荐的新型本地持久化组件,完全取代 SharedPreferences。它基于协程和 Flow 实现异步、线程安全的数据存储,天然支持响应式读取。DataStore 提供两种实现方式:
- Preferences DataStore:键值对存储,无类型约束,类似 SharedPreferences 但完全异步。
- Proto DataStore:使用 Protocol Buffers 定义结构化数据,在编译期保证类型安全,支持复杂对象。
核心优势:DataStore 的读操作始终返回 Flow,数据变更时自动通知所有订阅者,不再需要手动注册监听器。
4.2 Preferences DataStore 实现流程
4.2.1 添加依赖
// build.gradle (module)
implementation("androidx.datastore:datastore-preferences:1.1.1")
4.2.2 创建 DataStore
在顶层文件或单例中扩展 Context.preferencesDataStore 属性:
val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(
name = "settings"
)
4.2.3 实时读取
通过 dataStore.data 获得 Flow,指定键:
val userTheme: Flow<Boolean> = context.settingsDataStore.data.map { preferences ->
preferences[PreferenceKeys.THEME_KEY] ?: false
}
4.2.4 写入数据
suspend fun saveTheme(context: Context, isDark: Boolean) {
context.settingsDataStore.edit { preferences ->
preferences[PreferenceKeys.THEME_KEY] = isDark
}
}
object PreferenceKeys {
val THEME_KEY = booleanPreferencesKey("theme_dark")
}
关键点:Preferences DataStore 的键通过
xxxPreferencesKey 工厂方法创建,类型安全仅限于键的类型,但值仍是泛型。
4.3 Proto DataStore 实现流程
4.3.1 定义 Proto 文件
在 app/src/main/proto/ 目录下创建 user_prefs.proto:
syntax = "proto3";
option java_package = "com.example.app";
option java_multiple_files = true;
message UserPreferences {
bool theme_dark = 1;
int32 font_size = 2;
string display_name = 3;
}
4.3.2 添加 Protobuf 插件和依赖
// root build.gradle
plugins {
id "com.google.protobuf" version "0.9.4" apply false
}
// module build.gradle
plugins {
id "com.google.protobuf"
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.24.0" }
generateProtoTasks {
all().each { task ->
task.builtins {
java { option "lite" }
}
}
}
}
dependencies {
implementation "androidx.datastore:datastore:1.1.1"
implementation "com.google.protobuf:protobuf-javalite:3.24.0"
}
4.3.3 创建 Proto DataStore
val Context.userPreferencesDataStore: DataStore<UserPreferences> by dataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer()
)
4.3.4 实现序列化器
object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences
get() = UserPreferences.getDefaultInstance()
override fun readFrom(input: InputStream): UserPreferences {
return UserPreferences.parseFrom(input)
}
override fun writeTo(t: UserPreferences, output: OutputStream) {
t.writeTo(output)
}
}
4.3.5 类型安全的读取与写入
val displayName: Flow<String> = context.userPreferencesDataStore.data.map { prefs ->
prefs.displayName
}
suspend fun updateDisplayName(context: Context, name: String) {
context.userPreferencesDataStore.updateData { currentPrefs ->
currentPrefs.toBuilder()
.setDisplayName(name)
.build()
}
}
注意:Proto DataStore 的序列化器必须正确处理读写,若文件损坏会抛出
IOException。建议在 Flow 中使用 catch 操作符返回默认值。
4.4 实时读取与 Flow 操作符
DataStore 的 .data 属性返回 Flow<T>,可与标准 Flow 操作符组合:
| 操作符 | 用途 | 示例 |
|---|---|---|
map | 转换数据模型 | 将 Protocol Buffers 对象转为 UI 模型 |
distinctUntilChanged | 避免重复发射相同值 | 仅当实际值变化时才通知 UI |
catch | 捕获异常并返回默认值 | 文件损坏时返回存根 |
retry | 读取失败时重试 | 避免瞬态 I/O 错误 |
context.userPreferencesDataStore.data
.catch { e ->
emit(UserPreferences.getDefaultInstance()) // 降级
}
.map { it.themeDark }
.distinctUntilChanged()
.collect { isDark ->
// 更新 UI
}
4.5 性能优化与稳定性
- 批量写入:在
edit或updateData块内完成所有修改,避免多次提交。 - 减少全局 Flow 集合:使用
distinctUntilChanged避免不必要的触发。 - 文件大小控制:Proto DataStore 使用二进制格式,通常很小;Preferences DataStore 应避免存储大量数据。
- 异常处理:在
collect层使用catch并记录日志,防止应用崩溃。 - 内存泄漏预防:在 ViewModel 中使用
stateIn或shareIn配合viewModelScope自动取消。
4.6 常见错误与调试
| 错误表现 | 原因 | 解决方案 |
|---|---|---|
| 读取始终为默认值 | 未正确配置序列化器或键 | 检查 proto 文件生成代码和序列化器引用 |
| 写入后读取旧值 | Flow 未去重,或写入未提交 | 确认 edit/updateData 是挂起函数且调用在协程中 |
| 文件损坏导致崩溃 | 序列化器读取异常 | 添加 .catch 返回默认值;清除 data_store 目录测试 |
| 编译错误:未找到生成类 | Protobuf 插件未执行 | 运行 gradle clean 并检查 proto 路径 |
| 性能卡顿 | 主线程操作 DataStore | DataStore 本身是异步的,确保不在主线程收集 Flow |
调试技巧:在 Android Studio 的 Device File Explorer 中查看
/data/data/包名/files/datastore/ 下的文件,确认持久化内容。使用 Log.d 在 Flow 的 onStart 和 onEach 中打印每次发射的值。