第3章 DataStore替代方案:Preferences DataStore与Proto DataStore、协程支持

方案边界

DataStore 是 Jetpack 官方推荐的轻量级键值对与结构化数据存储方案,旨在替代 SharedPreferences。本章聚焦两种实现:Preferences DataStore(键值对,无类型约束)与 Proto DataStore(基于 Protocol Buffers 的强类型存储)。两者均基于协程与 Flow,天然支持异步与响应式。

  • 不覆盖:数据库级查询、跨进程通信、大文件存储。
  • 适用场景:应用配置、用户偏好、轻量级缓存(小于 100KB 的数据)。
  • 协程支持边界:仅 suspend 函数与 Flow 式读取,不可在回调/线程模型中直接使用。

分层职责

层次组件职责
UI / ViewModelFlow 收集订阅 DataStore 的 Flow,响应式更新 UI
仓库层 (Repository)DataStore 接口封装读写逻辑,管理协程作用域与异常处理
数据源层DataStore (Preferences/Proto)序列化/反序列化,文件 I/O 调度
持久化层本地文件 (单文件)物理存储(默认路径:data/data/…/datastore/)

数据流

DataStore 采用 单向数据流 + 协程异步:

  1. 读取:通过 .data 属性返回 Flow<T>,每次文件变更自动发射新值。
  2. 写入:调用 suspend 函数 edit() 或 updateData(),在 Dispatchers.IO 上执行事务性更新。
  3. 事务保证:写入操作自动加锁,同一 DataStore 实例的并发写排队执行;读操作始终读到最新提交值。
协程关键点:DataStore 的所有 I/O 操作必须运行在协程上下文中(如 viewModelScope、lifecycleScope),不可在非协程代码中直接调用。
DataStore 架构与数据流示意图 UI / ViewModel Repository DataStore 实例 Preferences DataStore Proto DataStore 本地文件 (磁盘) 协程作用域 (Dispatchers.IO) 读取:Flow 订阅,自动更新 写入:suspend edit() / updateData()

选型权衡

对比维度Preferences DataStoreProto DataStore
数据类型键值对(Int、String、Boolean、Float、Set)自定义 Protobuf 消息(强类型,支持嵌套结构)
序列化成本零搭建成本,无额外依赖需定义 .proto 文件并生成 Java/Kotlin 代码
类型安全运行时类型匹配(Preferences.Key)编译时完全类型安全(自动生成 getter/setter)
迁移成本可通过 SharedPreferencesMigration 直接迁移需自定义 DataMigration 或将旧数据映射为 Proto message
推荐场景简单键值配置(主题、语言、通知开关)结构化配置(用户设置对象、多字段复合数据)
工程建议:若项目已有 Protobuf 基础设施或数据结构会频繁增减字段,优先 Proto DataStore;否则从 Preferences 起步,遇结构化需求再升级。

风险点

  • 序列化异常:Proto DataStore 在反序列化旧格式或损坏文件时会抛出 InvalidProtocolBufferException,且无自动容错机制。建议在 Repository 层使用 catch 降级为默认值。
  • 单文件限制:DataStore 基于单文件实现,并发写入导致性能瓶颈(不如 Room 支持 WAL),不适合高频写入(>10次/秒)。
  • 协程泄漏:若未正确绑定作用域(如使用 GlobalScope),写入任务可能挂起而不被取消,导致内存泄漏。
  • 先写后读陷阱:写入后的第一次读取可能仍是旧值(因写入异步 commit 尚未完成),需在 edit 块内完成所有依赖逻辑。

落地步骤

  1. 添加依赖:
    // Preferences DataStore
    implementation("androidx.datastore:datastore-preferences:1.1.1")
    
    // Proto DataStore(额外)
    implementation("androidx.datastore:datastore:1.1.1")
    implementation("com.google.protobuf:protobuf-kotlin-lite:4.28.2")
  2. 定义数据模型(仅 Proto 需要):编写 app/src/main/proto/settings.proto,然后运行 Protobuf 编译插件生成 Kotlin 类。
  3. 初始化 DataStore:
    // Preferences
    val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")
    
    // Proto
    val Context.protoDataStore: DataStore<SettingsProto> by dataStore(
        fileName = "settings.proto",
        serializer = SettingsSerializer
    )
  4. 编写仓库封装:对外暴露 Flow<T> 和 suspend fun update(),内部处理异常与默认值。
  5. 注入协程作用域:在 ViewModel 中使用 viewModelScope 收集 Flow 或调用 update。
  6. 迁移旧数据(若从 SharedPreferences 迁移):在初始化时链式调用 SharedPreferencesMigration。

验收标准

  • ✅ 应用冷启动后,首次读取 DataStore 返回默认值,且不超过 300ms(中端设备)。
  • ✅ 连续写入 100 次(间隔 50ms),无数据丢失或 IOException。
  • ✅ 跨进程操作(如果涉及)不产生文件锁冲突。
  • ✅ Proto DataStore 新增字段后,旧版本保存的数据可正确反序列化,新字段返回 Protobuf 默认值。
  • ✅ 单元测试可通过 TestDispatcher 模拟协程,不依赖真实文件。
关键验收点:在 @OptIn(ExperimentalCoroutinesApi::class) 的测试中,确保写入后的 Flow 发射顺序与预期一致,避免 Conflated 机制导致的跳变。