DataStore 与 Flow 结合:Preferences DataStore、Proto DataStore、实时读取、类型安全

4.1 DataStore 概述与 Flow 原生集成

DataStore 是 Jetpack 官方推荐的新型本地持久化组件,完全取代 SharedPreferences。它基于协程和 Flow 实现异步、线程安全的数据存储,天然支持响应式读取。DataStore 提供两种实现方式:

  • Preferences DataStore:键值对存储,无类型约束,类似 SharedPreferences 但完全异步。
  • Proto DataStore:使用 Protocol Buffers 定义结构化数据,在编译期保证类型安全,支持复杂对象。
核心优势:DataStore 的读操作始终返回 Flow,数据变更时自动通知所有订阅者,不再需要手动注册监听器。
DataStore 与 Flow 实时数据流架构 应用层 写入操作 Flow 订阅 DataStore 层 Preferences DataStore Proto DataStore Flow 发射 持久化层 磁盘文件 Preferences DataStore 无类型约束,Proto DataStore 使用 protobuf 编译期生成类型安全代码 写入:DataStore.edit { } (挂起函数) 读取:dataStore.data (Flow,自动重发射最新值) 变换:map / distinctUntilChanged / catch 等 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 路径
性能卡顿主线程操作 DataStoreDataStore 本身是异步的,确保不在主线程收集 Flow
调试技巧:在 Android Studio 的 Device File Explorer 中查看 /data/data/包名/files/datastore/ 下的文件,确认持久化内容。使用 Log.d 在 Flow 的 onStart 和 onEach 中打印每次发射的值。