第3章:Preferences DataStore与Proto DataStore的区别、数据存储方式与序列化
DataStore 是 Jetpack 提供的轻量级数据存储方案,替代 SharedPreferences。它分为 Preferences DataStore 和 Proto DataStore 两种实现。本章从核心原理出发,对比两者的差异、数据存储方式、序列化机制,并给出实现流程、代码示例、调试要点及常见错误。
3.1 两种 DataStore 的核心差异
| 特性 | Preferences DataStore | Proto DataStore |
|---|---|---|
| 数据组织方式 | 键值对(Key-Value) | 类型化对象(Protobuf schema) |
| 序列化方式 | 自动序列化基本类型(Int、String、Boolean等) | 定义 .proto 文件,生成 Java/Kotlin 类,使用 Protocol Buffers 编码 |
| 类型安全 | 运行时类型检查,易出错 | 编译时类型安全,结构清晰 |
| 数据结构复杂度 | 仅支持简单键值,不支持嵌套 | 支持嵌套消息、枚举、列表等复杂结构 |
| 依赖大小 | 小,仅需 datastore-preferences | 较大,需引入 datastore + protobuf 插件 |
| 适用场景 | 简单配置项、开关、少量数据 | 结构化配置、表单数据、需版本管理的业务数据 |
3.2 数据存储方式
Preferences DataStore 底层使用 .preferences_pb 文件(Protocol Buffers 格式),但对外暴露简洁的键值 API。写入时先将整个文件反序列化,修改后重新写入磁盘,保证原子性。
Proto DataStore 直接操作 proto 定义的类对象,文件后缀默认为 .pb。序列化由 protobuf 编译器生成的高效二进制编码完成,支持向前/向后兼容。
存储路径:两种 DataStore 文件均位于
app/datastore/ 目录下(Context.filesDir)。文件名由开发者指定,例如 settings.preferences_pb 或 user_prefs.pb。
3.3 序列化内部机制
Preferences DataStore 内部使用 SharedPreferencesMigration 兼容旧数据,但自 1.1.0 版本起完全基于 protobuf。它将所有键值对转换为 protobuf 的 MapField,原型定义大致如下:
// PreferencesDataStore 内部 proto 定义(简化)
message Preferences {
map<string, Value> values = 1;
}
message Value {
oneof value {
int64 integer = 1;
float float_ = 2;
bool boolean = 3;
string string = 4;
bytes bytes = 5;
double double = 6;
}
}
Proto DataStore 则需要开发者自行编写 .proto 文件,例如:
syntax = "proto3";
option java_package = "com.example.app";
option java_multiple_files = true;
message UserSettings {
bool notifications_enabled = 1;
string display_name = 2;
int32 theme_mode = 3; // 0:light, 1:dark
repeated string favorite_tags = 4;
}
生成代码后,DataStore 框架自动调用 encode() 和 decode() 方法完成读写。
性能优化:Proto DataStore 减少 Java 反射,序列化/反序列化速度比 Preferences DataStore 快 2~5 倍(大数据量时尤其明显)。
3.4 实现流程对比
| 步骤 | Preferences DataStore | Proto DataStore |
|---|---|---|
| 1. 添加依赖 | implementation "androidx.datastore:datastore-preferences:1.1.1" | implementation "androidx.datastore:datastore:1.1.1" + protobuf 插件 & 依赖 |
| 2. 定义数据模型 | 不需要 | 编写 .proto 文件,并配置 protobuf 编译 |
| 3. 创建 DataStore 实例 | val Context.dataStore by preferencesDataStore(name = "settings") | val Context.userDataStore by dataStore(serializer = UserSettingsSerializer) |
| 4. 写入数据 | dataStore.edit { it[KEY_COUNTER] = 1 } | dataStore.updateData { it.copy(displayName = "New") } |
| 5. 读取数据 | dataStore.data.map { it[KEY_COUNTER] ?: 0 } | dataStore.data.first().displayName |
| 6. 序列化器实现 | 无需手动实现 | 实现 Serializer<T> 接口,提供 readFrom / writeTo |
3.5 内联 SVG:DataStore 两种实现原理对比图
3.6 代码示例:Preferences DataStore 完整读写
// 1. 创建委托属性(顶层扩展)
val Context.settingsDataStore by preferencesDataStore(name = "user_settings")
// 2. 定义键
private val KEY_THEME = stringPreferencesKey("theme")
private val KEY_NOTIFICATIONS = booleanPreferencesKey("notifications")
// 3. 写入
suspend fun saveTheme(context: Context, theme: String) {
context.settingsDataStore.edit { preferences ->
preferences[KEY_THEME] = theme
}
}
// 4. 读取(Flow)
val themeFlow: Flow<String> = context.settingsDataStore.data
.map { preferences ->
preferences[KEY_THEME] ?: "light"
}
3.7 代码示例:Proto DataStore 完整读写
// 1. 实现 Serializer
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)
}
}
// 2. 创建委托属性
val Context.userDataStore by dataStore(
fileName = "user.pb",
serializer = UserSettingsSerializer
)
// 3. 写入
suspend fun updateDisplayName(context: Context, name: String) {
context.userDataStore.updateData { currentSettings ->
currentSettings.toBuilder().setDisplayName(name).build()
}
}
// 4. 读取
val displayNameFlow: Flow<String> = context.userDataStore.data
.map { settings ->
settings.displayName
}
3.8 调试方法
- 查看存储文件:使用 Device File Explorer 或 adb 拉取文件:
adb pull /data/data/包名/datastore/ - Proto DataStore 调试:将 .pb 文件导出后,用
protoc --decode_raw查看二进制内容。 - Flow 测试:在 ViewModel 中
dataStore.data.first()获取当前快照;使用flow.collect观察变化。
常见错误1: 在 Proto DataStore 中未正确实现 Serializer,导致文件无法反序列化。务必重写 readFrom/writeFrom 方法,并使用 protobuf 提供的 parseFrom/writeTo。
常见错误2: Preferences DataStore 中使用不存在的 Key 类型(如使用 doublePreferencesKey 但实际存储 float),会抛出 ClassCastException。确保键类型与值类型一致。
常见错误2: Preferences DataStore 中使用不存在的 Key 类型(如使用 doublePreferencesKey 但实际存储 float),会抛出 ClassCastException。确保键类型与值类型一致。
3.9 性能与稳定性优化
| 优化项 | 方案 |
|---|---|
| 减少多次写入 | 合并 edit/updateData 调用,一次提交所有修改 |
| 避免大数据频繁读取 | 使用 distinctUntilChanged 过滤无效更新 |
| 文件过大 | 定期清理过期键或拆分多个 DataStore 实例(按职责) |
| 协程异常处理 | 在 ViewModel 中用 catch 捕获 DataStore 异常,避免崩溃 |
| 迁移旧数据 | Preferences DataStore 提供 SharedPreferencesMigration;Proto DataStore 可自定义迁移 |
选择建议:如果项目已有 SharedPreferences 且数据结构简单,先用 Preferences DataStore 迁移;若新项目且数据结构复杂,直接上 Proto DataStore,便于后期扩展。