简介
DataStore 是一种数据存储解决方案,允许您使用 ProtoBuf 存储键值对或类型化对象,由 Android 官方在 2020 年推出,并在 2021 年正式发布的库,旨在替代服役多年的 SharedPreferences。
SharedPreferences 的缺陷
- 文件数据的读取加锁,如果 SharedPreferences 文件未被加载或解析到内存中,读写操作都需要等待,可能会对 UI 线程流畅度造成一定影响,甚至ANR.
- 在保存数据时,无论是
commit()
还是apply()
都有可能引发 ANR 问题。 - 没有错误提示机制。
DataStore 的优点
- 基于 Flow 实现,保证主线程的安全性。
- 以事务方式处理更新数据。
- 可以监听到操作成功或者失败结果。
- 多进程使用。
Preferences DataStore 和 Proto DataStore
DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。
- Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
- Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用 ProtoBuf 来定义架构,但可以确保类型安全。
Preferences DataStore
Preferences DataStore 用于存储键值对,相当于 SharedPreferences 的改良版。
dependencies {
implementation("androidx.datastore:datastore-preferences:1.1.0")
}
实例:
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
读取:
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
支持的类型基本与 SharedPreferences 一致。
写入:
suspend fun incrementCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
转换块中的所有代码均被视为单个事务。
Proto DataStore
Proto DataStore 用于存储类型化对象,相当于 SharedPreferences 的升级版。它使用了 Protocol Buffers。
dependencies {
implementation("androidx.datastore:datastore:1.1.0")
}
在 app/src/main/proto/
目录下预定义:
// Settings.proto
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
用于告知 DataStore 如何读取和写入数据的序列化器:
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings = Settings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (e: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", e)
}
}
override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
}
实例:
// At the top level of your kotlin file:
val Context.settingsDataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
读取:
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data.map { settings ->
// The exampleCounter property is generated from the proto schema.
settings.exampleCounter
}
写入:
suspend fun incrementCounter() {
context.settingsDataStore.updateData {
it.toBuilder()
.setExampleCounter(it.exampleCounter + 1)
.build()
}
}
同样以事务方式更新数据。
从 SharedPreferences 中迁移
private val Context.migrationDataStore: DataStore<Preferences> by preferencesDataStore(
name = PREF_FILE_NAME,
produceMigrations = {
listOf(SharedPreferencesMigration(it, PREF_FILE_NAME))
}
)
数据的迁移在创建 DataStore 的过程中自动完成,迁移完成后,原 SharedPreferences 的 XML 文件会被删除。
对比 MMKV
MMKV 是微信团队开源的基于 mmap 内存映射的 Key-Value 组件,底层序列化与反序列化同样使用 ProtoBuf,性能高,稳定性强。
虽然 MMKV 的初衷并不是替代 SharedPreferences,但是同样作为 Key-Value 组件,大多数人都将 MMKV 视为 SharedPreferences 的替代品。
官方文档中也对 MMKV 和 SharedPreferences 的性能进行了对比:

事实上,MMKV 并不是任何时候都更强。由于内存映射这种方案是自行管理一块独立的内存,所以它在尺寸的伸缩上面就比较受限,这就导致它在写大一点的数据时,速度会慢。
另一方面,该写入耗时对于正常开发来说并非特别重要,界面的流畅度更在意主线程的耗时,而 SharedPreferences 本身也提供了异步写入的 API,所以它们都足够快了。但 MMKV 的诞生场景决定了,它更在意同步处理机制下的耗时。
MMKV 还有一个缺陷——丢数据。操作系统在往磁盘写数据的过程中发生意外都会导致文件损坏,这种问题不可避免。MMKV 虽然由于底层机制的原因,在程序崩溃的时候不会影响数据往磁盘的写入,但断电关机之类的操作系统级别的崩溃,MMKV 就没办法了,文件照样会损坏。
对于这种文件损坏,SharedPreferences 和 DataStore 的应对方式是在每次写入新数据之前都对现有文件做一次自动备份,这样在发生了意外出现了文件损坏之后,它们就会把备份的数据恢复过来。
而 MMKV,没有这种自动的备份和恢复,那么当文件发生了损坏,数据就丢了,之前保存的各种信息只能被重置。据官方统计,iOS 微信平均约有 70 万日次的数据校验不通过。
因此 MMKV 更适合同步高频写入非重要信息的场景。