第4章 选择DataStore类型:基于业务场景选择Preferences或Proto,定义数据模型

DataStore 提供两种底层实现:Preferences DataStore 和 Proto DataStore。选择错误的类型会导致后期序列化耦合、Schema 演进困难或性能瓶颈。本章从业务场景出发,建立类型选择模型,并针对 Proto DataStore 给出完整的数据模型定义流程。

4.1 两种 DataStore 核心差异

特性Preferences DataStoreProto DataStore
数据模型无固定 Schema,键值对基于 Protobuf 定义强类型 Schema
读写接口store.data 返回 Map<String, T>生成的 Java 类对象,直接访问字段
类型安全运行时类型转换(需自行 cast)编译期类型检查,自动序列化/反序列化
Schema 演进新增 Key 不影响旧数据需遵循 Protobuf 兼容规则(字段编号、类型)
跨进程共享理论上支持(不推荐)需额外配置 FileDescriptor
依赖体积极小(仅 core 库)需引入 protobuf-java 及 codegen 插件
适用场景简单配置、开关、用户偏好结构化数据、强类型校验、Schema 演进需求

4.2 选择决策流程

以下决策树可帮助你在实际项目中快速确定 DataStore 类型:

决策核心原则:
👉 如果数据是孤立的键值对(如 is_dark_mode、last_version_code),且未来不会演变为嵌套结构 → 选 Preferences。
👉 如果数据包含数组、枚举、嵌套对象,或需要向后兼容的 Schema 版本管理 → 选 Proto。
DataStore 类型选择决策流程 业务场景输入 数据结构 简单? 是 否 Preferences DataStore ● 键值对存储,无Schema ● 无类型安全,需手动转换 ● 适合简单配置项 Proto DataStore ● 强类型,编译期检查 ● 支持嵌套、枚举、数组 ● 需定义 .proto 文件 优先用 Preferences,直到数据复杂度证明需要 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 文件并处理兼容性。

4.5 常见错误与排错

  • ..proto 字段编号冲突: 删除字段时必须保留编号,使用 reserved 语法。错误示例:删除了编号 2 后又新增另一个 field 编号 2 → 导致旧数据反序列化异常。
  • Serializer 中使用 parseFrom 时未处理空文件: DataStore 首次创建时文件为空,parseFrom 会抛出异常。解决方案:在 readFrom 中捕获异常并返回 defaultValue,或使用 ReplaceFileCorruptionHandler。
  • Preferences Key 拼写错误: 因为无编译检查,运行时可能读取不到预期值。建议将 Key 定义在密封类或常量对象中。
性能陷阱:
不要频繁写入大量数据。每调用 edit 都会触发一次完整序列化。对于批量更新,尽量在一次 edit lambda 中完成所有字段赋值。Proto DataStore 的序列化开销高于 Preferences,但类型安全与 Schema 演进带来的收益通常更大。