第3章:Preferences DataStore与Proto DataStore的区别、数据存储方式与序列化

DataStore 是 Jetpack 提供的轻量级数据存储方案,替代 SharedPreferences。它分为 Preferences DataStore 和 Proto DataStore 两种实现。本章从核心原理出发,对比两者的差异、数据存储方式、序列化机制,并给出实现流程、代码示例、调试要点及常见错误。

3.1 两种 DataStore 的核心差异

特性Preferences DataStoreProto 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 DataStoreProto 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 两种实现原理对比图

Preferences DataStore 与 Proto DataStore 原理对比 Preferences DataStore 内部 Proto 自动生成 存储 .preferences_pb 文件 运行时 map<String, Value> 适用:简单键值,快速集成 序列化透明,无结构约束 Proto DataStore 开发者定义 .proto 文件 编译生成 Java/Kotlin 类 存储自定义 .pb 文件 适用:结构化数据,类型安全 需手动编写序列化器 核心区别:序列化控制权(系统 vs 开发者)及数据模型复杂度 两者都基于协程 Flow,保证线程安全和原子写入

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。确保键类型与值类型一致。

3.9 性能与稳定性优化

优化项方案
减少多次写入合并 edit/updateData 调用,一次提交所有修改
避免大数据频繁读取使用 distinctUntilChanged 过滤无效更新
文件过大定期清理过期键或拆分多个 DataStore 实例(按职责)
协程异常处理在 ViewModel 中用 catch 捕获 DataStore 异常,避免崩溃
迁移旧数据Preferences DataStore 提供 SharedPreferencesMigration;Proto DataStore 可自定义迁移
选择建议:如果项目已有 SharedPreferences 且数据结构简单,先用 Preferences DataStore 迁移;若新项目且数据结构复杂,直接上 Proto DataStore,便于后期扩展。