DataStore
DataStore是Jetpack近期新推出的组件,可以以下处理两种类型的数据持久化:
DataStore Type | Description |
---|---|
Preferences DataStore | 像SharedPreferences一样,以键值对的形式进行基本类型的数据存储。DataStore 基于 Flow 实现异步存储,避免因为阻塞主线程带来的ANR问题 |
Proto DataStore | 基于Protobuf实现任意自定义类型的数据存储,需要定义Protobuf的IDL,但是可以保证类型安全的访问 |
DataStore相对于SharedPreferences优点更多,可以完全替代SP的使用:
- 基于
Coroutine Flow
实现 - 保证数据访问一致性
- 异常处理机制
- 异步访问,避免同步阻塞
- 基于
Protobuf
,实现非基本型数据的存储
附上一张Google官方的对比表格:
Preferences DataStore
首先在build.gradle
中添加依赖,注意保证使用当前最新版本
// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"
如同SharedPreferences一样,创建一个DataStore
对象
val dataStore: DataStore<Preferences> =
context.createDataStore(name = "example-data-store-prefs")
为方便使用,我们分别创建读写的扩展方法:
fun <T> DataStore<Preferences>.getValueFlow(
key: Preferences.Key<T>,
defaultValue: T
): Flow<T> {
return this.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}.map { preferences ->
preferences[key] ?: defaultValue
}
}
suspend fun <T> DataStore<Preferences>.setValue(key: Preferences.Key<T>, value: T) {
this.edit { preferences ->
preferences[key] = value
}
}
写数据:
companion object {
private val USERNAME = preferencesKey<String>("username")
}
viewModelScope.launch {
dataStore.setValue(USERNAME, "Harry Potter")
}
读数据:
viewModelScope.launch {
dataStore.getValueFlow(USERNAME, "")
.collect { value ->
// use the value
}
}
异常处理:
viewModelScope.launch {
dataStore.getValueFlow(USERNAME, "")
.catch {
// handle error
}
.collect { value ->
// use the value
}
}
Proto DataStore
build.gradle
添加依赖
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0-alpha01"
当然,因为需要使用protobuf,所以别忘了添加protobuf的插件依赖,用来将生成代码
root的build.gradle
buildscript {
...
dependencies {
...
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.13'
...
}
...
}
然后应用此插件:
apply plugin: 'com.google.protobuf'
接下来,基于Protobuf定义一个Settings
类型
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
基于Serializer<T>
实现Settings
的序列化
object SettingsSerializer : Serializer<Settings> {
override fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override fun writeTo(
t: Settings,
output: OutputStream) = t.writeTo(output)
}
创建DataStore实例时,需要指定pb文件以及相关Serializer
类
val settingsDataStore: DataStore<Settings> = context.createDataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
写数据:
suspend fun incrementCounter() {
settingsDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setExampleCounter(currentSettings.exampleCounter + 1)
.build()
}
}
读数据:
val exampleCounterFlow: Flow<Int> = settingsDataStore.data
.map { settings ->
// The exampleCounter property is generated from the proto schema.
settings.exampleCounter
}
SharedPreferences迁移
当我们需要将数据从SharedPreferences迁移到DataStore时,可以在创建DataStore实例时通过SharedPreferencesMigration
指定SP的名字:
val dataStore: DataStore<Preferences> =
context.createDataStore(
name = "example-data-store-prefs",
migrations = listOf(SharedPreferencesMigration(context, "example-prefs"))
)
通过SharedPreferencesMigration
的签名可以清晰得知其他的参数功能:
fun SharedPreferencesMigration(
context: Context,
sharedPreferencesName: String,
keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS,
deleteEmptyPreferences: Boolean = true
)
不足
最后说几点DataStore
的不足:
- 目前Jetpack Security没有支持DataStore,所以不能像SharedPreference一样支持加密
- 不能安全的进行IPC,这点相对于SharedPreferences没有提升,有较强IPC需求的话首选MMKV
- 使用PB进行序列化时需要额外定义IDL,这会产生一定工作量
官方文档:
https://developer.android.com/topic/libraries/architecture/datastore