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 迁移场景决策流程图

DataStore 类型选型决策流程 SharedPreferences 迁移需求 需要结构化 Schema? Proto DataStore 选型 需定义 .proto 文件 需管理 Schema 版本 迁移成本较高 仅有简单键值对 未来可能升级为结构化? Proto DataStore 选型 Preferences DataStore 选型 无需 .proto 文件 迁移速度快、成本低 最终建议:不确定时优先选择 Proto DataStore

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 定义中保留原键名映射即可。