第4章 选择DataStore类型:基于业务场景选择Preferences或Proto,定义数据模型
DataStore 提供两种底层实现:Preferences DataStore 和 Proto DataStore。选择错误的类型会导致后期序列化耦合、Schema 演进困难或性能瓶颈。本章从业务场景出发,建立类型选择模型,并针对 Proto DataStore 给出完整的数据模型定义流程。
4.1 两种 DataStore 核心差异
| 特性 | Preferences DataStore | Proto DataStore |
|---|---|---|
| 数据模型 | 无固定 Schema,键值对 | 基于 Protobuf 定义强类型 Schema |
| 读写接口 | store.data 返回 Map<String, T> | 生成的 Java 类对象,直接访问字段 |
| 类型安全 | 运行时类型转换(需自行 cast) | 编译期类型检查,自动序列化/反序列化 |
| Schema 演进 | 新增 Key 不影响旧数据 | 需遵循 Protobuf 兼容规则(字段编号、类型) |
| 跨进程共享 | 理论上支持(不推荐) | 需额外配置 FileDescriptor |
| 依赖体积 | 极小(仅 core 库) | 需引入 protobuf-java 及 codegen 插件 |
| 适用场景 | 简单配置、开关、用户偏好 | 结构化数据、强类型校验、Schema 演进需求 |
4.2 选择决策流程
以下决策树可帮助你在实际项目中快速确定 DataStore 类型:
决策核心原则:
👉 如果数据是孤立的键值对(如
👉 如果数据包含数组、枚举、嵌套对象,或需要向后兼容的 Schema 版本管理 → 选 Proto。
👉 如果数据是孤立的键值对(如
is_dark_mode、last_version_code),且未来不会演变为嵌套结构 → 选 Preferences。👉 如果数据包含数组、枚举、嵌套对象,或需要向后兼容的 Schema 版本管理 → 选 Proto。
4.3 定义 Proto DataStore 数据模型
当确定使用 Proto DataStore 后,需要依次完成:定义 .proto 文件 → 配置 Gradle Protobuf 插件 → 编写 Serializer 实现 → 构建 DataStore<UserPreferences>。
4.3.1 编写 .proto 文件
示例:
syntax = "proto3";
package com.example.settings;
option java_package = "com.example.settings.datastore";
option java_multiple_files = true;
message UserPreferences {
// 字段编号必须保持不变,新增字段使用新编号
bool use_dark_mode = 1;
string display_name = 2;
int32 fontSize_sp = 3;
repeated Bookmark bookmarks = 4;
message Bookmark {
int32 id = 1;
string title = 2;
string url = 3;
}
}
4.3.2 Gradle 依赖与插件配置
在 app 模块的 build.gradle.kts 中添加:
plugins {
id("com.google.protobuf") version "0.9.4"
}
android {
// ...
}
dependencies {
implementation("androidx.datastore:datastore:1.1.2")
implementation("androidx.datastore:datastore-rxjava3:1.1.2") // 可选RxJava
implementation( "com.google.protobuf:protobuf-javalite:4.28.2")
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.28.2"
}
generateProtoTasks {
all().forEach { task ->
task.plugins {
id("javalite") { }
}
}
}
}
4.3.3 实现 Serializer
import androidx.datastore.core.Serializer
import com.example.settings.datastore.UserPreferences
import java.io.InputStream
import java.io.OutputStream
object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences
get() = UserPreferences.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserPreferences {
return UserPreferences.parseFrom(input)
}
override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
t.writeTo(output)
}
}
4.3.4 在 ViewModel 中创建 DataStore
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.datastore.createDataStore
import com.example.settings.datastore.UserPreferences
import kotlinx.coroutines.flow.Flow
object DataStoreProvider {
private lateinit var dataStore: DataStore<UserPreferences>
fun initialize(context: Context) {
dataStore = context.createDataStore(
fileName = "user_preferences.pb",
serializer = UserPreferencesSerializer,
corruptionHandler = ReplaceFileCorruptionHandler {
UserPreferences.getDefaultInstance()
}
)
}
fun getInstance(): DataStore<UserPreferences> = dataStore
}
在 Application 类中初始化:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
DataStoreProvider.initialize(this)
}
}
4.4 Preferences DataStore 简单示例(无模型定义)
若选择 Preferences,直接使用 preferencesDataStore 委托即可,无需自定义模型:
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private val Context.dataStore by preferencesDataStore(name = "settings")
class SettingsRepository(private val context: Context) {
private object Keys {
val DISPLAY_NAME = stringPreferencesKey("display_name")
}
val displayName: Flow<String> = context.dataStore.data.map { prefs ->
prefs[Keys.DISPLAY_NAME] ?: ""
}
suspend fun setDisplayName(name: String) {
context.dataStore.edit { prefs ->
prefs[Keys.DISPLAY_NAME] = name
}
}
}
选择建议:
在 MVP/POC 阶段优先使用 Preferences。只有当数据字段超过 5 个、需要嵌套、或预期长期维护时才迁移到 Proto。Proto 的 Schema 演进能力强,但需要额外维护 .proto 文件并处理兼容性。
在 MVP/POC 阶段优先使用 Preferences。只有当数据字段超过 5 个、需要嵌套、或预期长期维护时才迁移到 Proto。Proto 的 Schema 演进能力强,但需要额外维护 .proto 文件并处理兼容性。
4.5 常见错误与排错
- .
.proto字段编号冲突: 删除字段时必须保留编号,使用reserved语法。错误示例:删除了编号 2 后又新增另一个 field 编号 2 → 导致旧数据反序列化异常。 - Serializer 中使用
parseFrom时未处理空文件: DataStore 首次创建时文件为空,parseFrom会抛出异常。解决方案:在readFrom中捕获异常并返回defaultValue,或使用ReplaceFileCorruptionHandler。 - Preferences Key 拼写错误: 因为无编译检查,运行时可能读取不到预期值。建议将 Key 定义在密封类或常量对象中。
性能陷阱:
不要频繁写入大量数据。每调用
不要频繁写入大量数据。每调用
edit 都会触发一次完整序列化。对于批量更新,尽量在一次 edit lambda 中完成所有字段赋值。Proto DataStore 的序列化开销高于 Preferences,但类型安全与 Schema 演进带来的收益通常更大。