第3章 DataStore替代方案:Preferences DataStore与Proto DataStore、协程支持
方案边界
DataStore 是 Jetpack 官方推荐的轻量级键值对与结构化数据存储方案,旨在替代 SharedPreferences。本章聚焦两种实现:Preferences DataStore(键值对,无类型约束)与 Proto DataStore(基于 Protocol Buffers 的强类型存储)。两者均基于协程与 Flow,天然支持异步与响应式。
- 不覆盖:数据库级查询、跨进程通信、大文件存储。
- 适用场景:应用配置、用户偏好、轻量级缓存(小于 100KB 的数据)。
- 协程支持边界:仅
suspend函数与 Flow 式读取,不可在回调/线程模型中直接使用。
分层职责
| 层次 | 组件 | 职责 |
|---|---|---|
| UI / ViewModel | Flow 收集 | 订阅 DataStore 的 Flow,响应式更新 UI |
| 仓库层 (Repository) | DataStore 接口 | 封装读写逻辑,管理协程作用域与异常处理 |
| 数据源层 | DataStore (Preferences/Proto) | 序列化/反序列化,文件 I/O 调度 |
| 持久化层 | 本地文件 (单文件) | 物理存储(默认路径:data/data/…/datastore/) |
数据流
DataStore 采用 单向数据流 + 协程异步:
- 读取:通过
.data属性返回Flow<T>,每次文件变更自动发射新值。 - 写入:调用
suspend函数edit()或updateData(),在Dispatchers.IO上执行事务性更新。 - 事务保证:写入操作自动加锁,同一 DataStore 实例的并发写排队执行;读操作始终读到最新提交值。
协程关键点:DataStore 的所有 I/O 操作必须运行在协程上下文中(如
viewModelScope、lifecycleScope),不可在非协程代码中直接调用。选型权衡
| 对比维度 | Preferences DataStore | Proto 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块内完成所有依赖逻辑。
落地步骤
- 添加依赖:
// 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") - 定义数据模型(仅 Proto 需要):编写
app/src/main/proto/settings.proto,然后运行 Protobuf 编译插件生成 Kotlin 类。 - 初始化 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 ) - 编写仓库封装:对外暴露
Flow<T>和suspend fun update(),内部处理异常与默认值。 - 注入协程作用域:在 ViewModel 中使用
viewModelScope收集 Flow 或调用update。 - 迁移旧数据(若从 SharedPreferences 迁移):在初始化时链式调用
SharedPreferencesMigration。
验收标准
- ✅ 应用冷启动后,首次读取 DataStore 返回默认值,且不超过 300ms(中端设备)。
- ✅ 连续写入 100 次(间隔 50ms),无数据丢失或
IOException。 - ✅ 跨进程操作(如果涉及)不产生文件锁冲突。
- ✅ Proto DataStore 新增字段后,旧版本保存的数据可正确反序列化,新字段返回 Protobuf 默认值。
- ✅ 单元测试可通过
TestDispatcher模拟协程,不依赖真实文件。
关键验收点:在
@OptIn(ExperimentalCoroutinesApi::class) 的测试中,确保写入后的 Flow 发射顺序与预期一致,避免 Conflated 机制导致的跳变。