2. DataStore 类型选型:Preferences vs Proto DataStore
在启动 SharedPreferences → DataStore 迁移前,必须先确定目标 DataStore 类型。选型错误会导致数据结构不兼容、序列化性能下降或未来扩展困难。Preferences DataStore 和 Proto DataStore 使用完全不同的存储模型,迁移策略也因此不同。
2.1 两种 DataStore 的核心差异
| 对比维度 | Preferences DataStore | Proto DataStore |
|---|---|---|
| 数据结构 | 无类型键值对(类似 SharedPreferences) | 强类型 Protobuf Schema 定义 |
| 类型安全 | 运行时依赖类型推断 | 编译时类型检查 |
| Schema 定义 | 不需要 | 必须定义 .proto 文件 |
| 序列化方式 | 内置 JSON-like 处理 | ProtoBuf 二进制序列化 |
| 迁移复杂度 | 低(映射已有键即可) | 高(需定义 Schema 并处理版本兼容) |
| 适用场景 | 简单配置、开关、字符串偏好 | 结构化数据、嵌套对象、需要向后兼容 |
2.2 迁移场景判断依据
对照下表快速判断当前项目应选择哪种 DataStore 进行迁移。每个指标独立决策,只要命中一条红线,优先采用 Proto DataStore。
| 判断指标 | 推荐 Preferences | 推荐 Proto |
|---|---|---|
| 键总数 | < 20 个 | ≥ 20 个,或存在嵌套关系 |
| 值类型范围 | 仅 String / Int / Boolean / Float / Long | 包含自定义对象、枚举、列表或 Map |
| 未来字段新增频率 | 几乎不变 | 每月新增或变动字段(需版本兼容) |
| 多进程读写 | 否 | 是(Proto 提供更好的并发控制) |
| 跨平台/跨语言接入 | 否 | 是(ProtoBuf 天生支持多语言序列化) |
| 迁移数据量 | < 100 KB | ≥ 100 KB,或包含二进制数据 |
2.3 迁移场景决策流程图
2.4 代码实例:两种 DataStore 依赖配置与基本使用
根据选型结果,在 build.gradle 中添加对应依赖。Preferences 需要 lifecycle-livedata-ktx 配合 DataStore 使用 map 操作;Proto 需要额外引入 protobuf 插件。
Preferences DataStore 依赖与初始化
// build.gradle (app)
dependencies {
implementation "androidx.datastore:datastore-preferences:1.1.1"
// 配合生命周期使用(可选)
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
}
// 在 Activity/Fragment/ViewModel 中创建
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = "user_settings"
)
Proto DataStore 依赖与初始化
// build.gradle (app)
plugins {
id "com.google.protobuf" version "0.9.4"
}
dependencies {
implementation "androidx.datastore:datastore:1.1.1"
implementation "com.google.protobuf:protobuf-javalite:3.25.3"
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.25.3" }
generateProtoTasks {
all().each { task ->
task.builtins {
java { option "lite" }
}
}
}
}
// app/src/main/proto/user_settings.proto
syntax = "proto3";
option java_package = "com.example.app.datastore";
option java_multiple_files = true;
message UserSettings {
bool dark_mode = 1;
int32 font_size = 2;
string language = 3;
}
// 创建序列化器
package com.example.app.datastore
object UserSettingsSerializer : Serializer<UserSettings> {
override val defaultValue: UserSettings = UserSettings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserSettings {
return UserSettings.parseFrom(input)
}
override suspend fun writeTo(t: UserSettings, output: OutputStream) {
t.writeTo(output)
}
}
// 在 ViewModel 中创建 DataStore
val Context.userSettingsDataStore: DataStore<UserSettings> by dataStore(
fileName = "user_settings.pb",
serializer = UserSettingsSerializer
)
迁移场景判断总结:如果 SharedPreferences 存储的键值对在未来半年内不会引入复杂结构,且单文件数据量小于 100 KB,使用 Preferences DataStore 迁移最快。否则应选择 Proto DataStore,虽然前期需要定义 .proto 文件和序列化器,但在字段新增、类型变更、跨进程共享时能避免大量重构。
2.5 常见选型陷阱
- 混合使用两种 DataStore:同一个 app 可以同时存在 Preferences 和 Proto DataStore,但不要对同一份数据混合读写。迁移时确定一份目标类型,不要中途切换。
- 用 Preferences 模拟结构化数据:将 JSON 字符串存入 Preferences DataStore 的 String 键中,违背了 DataStore 的设计初衷,且无法利用 ProtoBuf 的版本兼容能力。
- 忽略 Proto DataStore 的版本兼容配置:定义 .proto 文件时务必保留原始字段编号,新增字段只能使用新编号,禁止修改已有字段编号或类型,否则会导致序列化异常。
推荐策略:如果项目处于初期阶段且不确定未来扩展,先以 Preferences DataStore 完成迁移,待业务稳定后再使用 Proto DataStore 重构。DataStore 支持从 Preferences 到 Proto 的二次迁移,只需在新的 .proto 定义中保留原键名映射即可。