Google 推出了 JetPack 的新成员 DataStore,DataStore 是一种新的数据存储方案。DataStore 以异步、一致的事务方式存储数据,克服了 SharedPreferences 的一些缺点。
Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和流程构建而成,提供两种不同的实现:
- Proto DataStore,它允许您存储类型化的对象(由协议缓冲区提供支持)
- Preferences DataStore,用于存储键值对
以异步、一致的事务方式存储数据,克服了 SharedPreferences 的大部分缺点。
Google 的目的很明确,就是打算用来取代 SharedPreferences,相信很快 SharedPreferences 就会变成 Deprecated 了。DataStore 提供了两种不同的实现:Preferences DataStore 和 Proto DataStore。DataStore 和 SharedPreferences 的数据存储方案对比如下所示:(链接,链接可能访问不了,多访问几次)
Feature |
SharedPreferences |
PreferencesDataStore |
ProtoDataStore |
Async API |
✅ (only for reading changed values, via listener) |
✅ (via |
✅ (via |
Synchronous API |
✅ (but not safe to call on UI thread) |
❌ |
❌ |
Safe to call on UI thread |
❌* |
✅ (work is moved to |
✅ (work is moved to |
Can signal errors |
❌ |
✅ |
✅ |
Safe from runtime exceptions |
❌** |
✅ |
✅ |
Has a transactional API with strong consistency guarantees |
❌ |
✅ |
✅ |
Handles data migration |
❌ |
✅ (from SharedPreferences) |
✅ (from SharedPreferences) |
Type safety |
❌ |
❌ |
✅ with Protocol Buffers |
目前 DataStore 还处于 alpha 版本,要使用 DataStore 需要添加的依赖如下:
dependencies {
// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
}
DataStore 分为 Preferences DataStore 和 Proto DataStore。
Preferences DataStore 是由类 DataStore 和 Preferences 实现,用于存储简单的键值对到磁盘。
private val DATASTORE_PREFERENCE_NAME = "DataStorePreference"//定义 DataStore 的名字
mDataStorePre = this.createDataStore(
name = DATASTORE_PREFERENCE_NAME
)
createDataStore 是 Context 的一个扩展方法:
fun Context.createDataStore(
name: String,
corruptionHandler: ReplaceFileCorruptionHandler? = null,
migrations: List> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): DataStore =
PreferenceDataStoreFactory.create(
produceFile = {
File(this.filesDir, "datastore/$name.preferences_pb")
},
corruptionHandler = corruptionHandler,
migrations = migrations,
scope = scope
)
private suspend fun savePreInfo(value: String) {
var preKey = preferencesKey(PREFERENCE_KEY_NAME)
mDataStorePre.edit { mutablePreferences ->
mutablePreferences[preKey] = value
}
}
private suspend fun readPreInfo(): String {
var preKey = preferencesKey(PREFERENCE_KEY_NAME)
var value = mDataStorePre.data.map { preferences ->
preferences[preKey] ?: ""
}
return value.first()
}
Preferences DataStore 以键值对的形式存储在本地,首先应该定义一个 Key:
var preKey = preferencesKey(PREFERENCE_KEY_NAME)
key 的类型是 Preferences.Key
inline fun preferencesKey(name: String): Preferences.Key {
return when (T::class) {
Int::class -> {
Preferences.Key(name)
}
String::class -> {
Preferences.Key(name)
}
Boolean::class -> {
Preferences.Key(name)
}
Float::class -> {
Preferences.Key(name)
}
Long::class -> {
Preferences.Key(name)
}
Set::class -> {
throw IllegalArgumentException("Use `preferencesSetKey` to create keys for Sets.")
}
else -> {
throw IllegalArgumentException("Type not supported: ${T::class.java}")
}
}
}
Preferences DataStore 中是通过 DataStore.edit() 写入数据,edit 方法是个 suspend 函数,必须在协程中进行调用;通过 DataStore.data 去读取数据,返回的是一个 Flow
具体的使用例子如下:
R.id.btn_pre_save -> {
var textPre = edit_pre.text.trim().toString()
lifecycleScope.launch {
savePreInfo(textPre)
}
}
R.id.btn_pre_read -> {
lifecycleScope.launch {
var value = readPreInfo()
Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
}
}
Google 推出 DataStore 的目的是为了取代 SharedPreference,对于老项目,就需要从 SharedPreference 中进行数据的迁移,从 SharedPreference 迁移到 DataStore。
在 3.1.1 中 createDataStore 方法的参数中有 migrations 参数:
migrations: List> = listOf()
只需要在 createDataStore 方法中按照如下格式就可以自动完成数据的迁移:
mDataStorePre = this.createDataStore(
name = DATASTORE_PREFERENCE_NAME,
migrations = listOf(SharedPreferencesMigration(this, SP_PREFERENCE_NAME))
)
Proto DataStore 是通过 protocol buffers 将对象序列化存储在磁盘。Protocol buffers 是什么,在这之前我听都没听过,怎么办,学呗。
Protocol buffers 的介绍可以参考:
https://developers.google.cn/protocol-buffers
在这里,只需要知道:
Protocol Buffers (ProtocolBuffer/ protobuf )是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。同XML相比,Protocol buffers在序列化结构化数据方面有许多优点:
更简单
数据描述文件只需原来的1/10至1/3
解析速度是原来的20倍至100倍
减少了二义性
生成了更容易在编程中使用的数据访问类
在创建 Proto DataStore 的时候,在 AndroidStudio 中,必须先做如下配置:
在 project 的 build.gradle 中添加依赖:
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
在 app 的 build.gradle 中,修改的比较多,所以整个文件都贴出来:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.protobuf'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "cn.zzw.datastore"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
dataBinding true
}
kotlinOptions {
jvmTarget = "1.8"
}
sourceSets {
main {
proto {
srcDir 'src/main/proto'
include '**/*.proto'
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "com.google.protobuf:protobuf-javalite:3.10.0"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.10.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
接着,在目录 app/src/main/proto 创建文件 user_prefs.proto:
syntax = "proto3";
option java_package = "cn.zzw.datastore";
option java_multiple_files = true;
message UserPreferences {
int32 id = 1;
string name = 2;
int32 age = 3;
string phone = 4;
}
记得要执行 rebuild project 。
接着创建 UserPreferencesSerializer:
package cn.zzw.datastore
import androidx.datastore.Serializer
import java.io.InputStream
import java.io.OutputStream
object UserPreferencesSerializer : Serializer {
override fun readFrom(input: InputStream): UserPreferences {
return UserPreferences.parseFrom(input)
}
override fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
最后创建 Proto DataStore :
mDataStorePro =
this.createDataStore(
fileName = "user_pros.pb",
serializer = UserPreferencesSerializer
)
private suspend fun saveProInfo(value: String) {
mDataStorePro.updateData { preferences ->
preferences.toBuilder().setId(110).setName(value).setAge(39).setPhone("119120").build()
}
}
private suspend fun readProInfo(): String {+
val userPreferencesFlow: Flow = mDataStorePro.data
return userPreferencesFlow.first().toString()
}
调用如下:
R.id.btn_pro_save -> {
var textPre = edit_pro.text.trim().toString()
lifecycleScope.launch {
saveProInfo(textPre)
}
}
R.id.btn_pro_read -> {
lifecycleScope.launch {
var value = readProInfo()
Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
}
}
目前 DataStore 还处于 alpha 版本,等到正式版本出来后,还是要考虑下用它来替换 SharedPreference。DataStore 和 SharedPreference 一样适合用来存储小且简单的数据,如果是较多的数据,还是推荐用 Room。此篇只是记录了 DataStore 的基础用法,等后续正式版本出来后,再来研究下它的源码,看它是如何实现。
参考:
https://developer.android.google.cn/topic/libraries/architecture/datastore
https://codelabs.developers.google.com/codelabs/android-preferences-datastore#5
https://scalereal.com/android/2020/09/03/hello-datastore-bye-sharedpreferences-android.html