DataStore — 来自 Jetpack 库的组件,用于在 Android 应用中存储少量数据。与 SharedPreferences 不同,它异步工作,并保证在并发访问时数据的一致性。根据 Google, 2024 的数据,DataStore 使用 Kotlin Coroutines 和 Flow,使其对主线程安全并适用于响应式架构。
要点
DataStore — Google 在 Android 中用于本地数据存储的解决方案,于 2020 年作为 SharedPreferences 的替代方案推出。它支持两种模式:Preferences DataStore(简单的键值对)和 Proto DataStore(基于 Protocol Buffers 的类型化模式)。
主要优势 — 完全异步:所有读取操作从 Kotlin Coroutines 返回 Flow,写入在协程上下文中执行。这消除了主线程阻塞,这是 SharedPreferences 在处理大量数据时的典型问题。
DataStore 保证操作的原子性:由于事务模型,并发写入不会导致数据丢失。如果两个组件同时修改同一值,DataStore 通过 compare-and-swap 机制正确处理冲突。
根据 Google I/O 2023 的数据,DataStore 在 40% 的 Android 新项目中使用,Google 建议在需要设置存储稳定性的所有应用中从 SharedPreferences 迁移。
DataStore 的基础是 SingleProcessDataStore — 在单个进程范围内工作的实现。它使用带有文件级锁的文件存储:在写入数据时,文件被锁定,防止并发访问时的损坏。
DataStore 自动处理反序列化错误:如果文件损坏,它返回默认值并重写文件。此行为通过 corruptionHandler 配置,该处理程序可在创建 DataStore 时设置。
SharedPreferences 存在三个基本问题:主线程上的同步磁盘读取、并发写入时缺乏原子性保证以及无法响应式跟踪更改。DataStore 解决了所有三个问题:Flow 用于观察,文件锁用于原子性,异步 API 用于线程安全。
DataStore 将数据存储在设备内部存储的文件中。Preferences DataStore 使用与 SharedPreferences 类似的文件格式,但具有用于完整性检查的额外元数据。Proto DataStore 使用 Protocol Buffers 的二进制格式,这减小了文件大小并加快了序列化速度。
在读取数据时,DataStore 一次性将整个文件加载到内存中,之后订阅者通过 Flow 接收当前状态。更改自动传输给所有活动订阅者 — 不需要像 SharedPreferences 那样手动注册监听器。
Preferences DataStore 使用基于 Map 的内置序列化机制。每个条目是一个字符串和原始类型(Int、Boolean、Float、Long、String、Set)的对。数据存储在 XML 文件中,类似于 SharedPreferences,但通过文件锁实现原子写入。
创建 Preferences DataStore 的示例:Context 上的 preferencesDataStore 扩展创建一个带有文件名的单例。在重复调用时返回同一实例 — 这消除了文件重复和不同存储实例的混淆。
Proto DataStore 需要通过 .proto 文件定义数据模式并使用 protobuf 插件进行编译。生成的 Java 类用作所有字段的唯一入口点 — 这消除了 SharedPreferences 中常见的键拼写错误。
Proto DataStore 模式定义一次,并支持在不丢失旧数据的情况下添加新字段。如果在应用程序的新版本中添加具有默认值的字段,旧文件将被正确反序列化 — 向后兼容性 已内置于协议中。
Preferences DataStore 和 Proto DataStore 之间的选择取决于数据的复杂性和类型化要求。两种变体都是异步和事务性的,但在类型安全级别和序列化性能上有所不同。
| 特性 | Preferences DataStore | Proto DataStore |
|---|---|---|
| 类型化 | 弱(键值) | 强(生成的类) |
| 序列化 | XML(内置) | Protocol Buffers(protobuf) |
| 文件大小 | 大(可读 XML) | 小(二进制) |
| 复杂性 | 低(无 .proto) | 中(需要 .proto) |
| 模式迁移 | 无模式 | 自动(proto) |
| 兼容性 | SharedPreferences(通过迁移) | 仅 Proto DataStore |
Preferences DataStore 适用于简单设置:功能启用标志、授权令牌字符串、应用程序启动次数。如果数据量少(最多 10–15 个键)且不需要严格模式 — Preferences DataStore 提供最低入门门槛,无需连接 protobuf 插件。
Proto DataStore 在数据结构复杂或可能在应用程序版本之间变化时是合理的。例如,用户配置文件设置或具有 20+ 字段的 A/B 测试配置。Protobuf 提供强类型化和自动迁移,这消除了因键不匹配而导致的运行时错误。
Google 通过 SharedPreferencesMigration 类提供内置迁移机制。迁移在应用程序更新后的首次启动时执行一次:DataStore 从 SharedPreferences 读取数据,以自身格式写入,并将迁移标记为完成。
迁移支持自定义转换:如果 SharedPreferences 中的键与所需的 DataStore 键不匹配,可以通过 SharedPreferencesMigration 定义转换函数。这允许在迁移过程中重命名键和更改数据类型。
第一步:将 DataStore 添加到 build.gradle 并创建带有迁移的 DataStore 实例:SharedPreferencesMigration 接受 SharedPreferences 文件名和要转移的键集。第二步:删除所有通过 SharedPreferences 工作的代码,并将其替换为 DataStore 调用。第三步 — 测试迁移:在首次启动时,数据应出现在 DataStore 中,旧的 SharedPreferences 文件应停止使用。
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore 易于集成到现有项目中。以下是 Preferences DataStore 和 Proto DataStore 的实用示例 — 两者都演示了数据的读取、写入和响应式观察。
在此示例中,Preferences DataStore 存储三个设置:深色主题、用户名和启动次数。读取通过返回 Flow 的 .data 扩展执行。写入 — 通过 .edit 挂起函数,保证更改的原子性。
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore 需要定义 .proto 文件。编译后,创建用于读取和写入的 UserSettings 类。模式版本的迁移在同一个 .proto 文件中描述并自动应用。
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// 从 DataStore 读取
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// 写入新值
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore 通过 ViewModel 与 MVVM 架构集成。来自 DataStore 的 Flow 通过 .stateIn 收集并在 UI 中使用。每次数据更改时,UI 自动更新 — 不需要手动更新或 LiveData。
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
常见问题
DataStore 异步工作(不阻塞 UI 线程),通过事务支持并发访问,并允许通过 Flow 响应式订阅更改。SharedPreferences — 同步 API,大数据量时有 ANR 风险,且没有内置的响应式支持。
DataStore 是用 Kotlin 编写的,需要 Kotlin Coroutines。从 Java 使用它是可能的,但不方便:必须使用 CompletableFuture 创建包装器或手动管理协程。对于 Java 项目,Google 建议保留 SharedPreferences 或向模块添加 Kotlin。
DataStore 在读取时将整个文件加载到内存中,因此不适合存储列表或大型对象。对于此类场景,请使用 Room 或 SQLite。DataStore 针对设置和小型结构化数据进行了优化 — 可达数百 KB。
在创建 DataStore 时可以传递 corruptionHandler — 在文件损坏时调用的函数。默认情况下,DataStore 抛出 CorruptionException。在 corruptionHandler 中可以返回空数据,之后 DataStore 将用正确状态重写文件。
是的,Proto DataStore 需要在 .proto 文件中定义模式并连接 protobuf-gradle-plugin。如果项目较小且数据简单,使用 Preferences DataStore 更容易 — 它不需要额外的构建配置。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。