赞
踩
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<Preferences>? = null,
- migrations: List<DataMigration<Preferences>> = listOf(),
- scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
- ): DataStore<Preferences> =
- 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<String>(PREFERENCE_KEY_NAME)
- mDataStorePre.edit { mutablePreferences ->
- mutablePreferences[preKey] = value
- }
- }
-
- private suspend fun readPreInfo(): String {
- var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
- var value = mDataStorePre.data.map { preferences ->
- preferences[preKey] ?: ""
- }
- return value.first()
- }
Preferences DataStore 以键值对的形式存储在本地,首先应该定义一个 Key:
var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
key 的类型是 Preferences.Key<T>,但是只支持 Int , String, Boolean , Float , Long 类型:
- inline fun <reified T : Any> preferencesKey(name: String): Preferences.Key<T> {
- return when (T::class) {
- Int::class -> {
- Preferences.Key<T>(name)
- }
- String::class -> {
- Preferences.Key<T>(name)
- }
- Boolean::class -> {
- Preferences.Key<T>(name)
- }
- Float::class -> {
- Preferences.Key<T>(name)
- }
- Long::class -> {
- Preferences.Key<T>(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<T> 。
具体的使用例子如下:
- 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<DataMigration<Preferences>> = 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<UserPreferences> {
- 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<UserPreferences> = 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
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。