当前位置:   article > 正文

Android Jetpack 之 DataStore_androidx.datastore:datastore-preferences

androidx.datastore:datastore-preferences

1. 概述

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 Flow)

✅ (via Flow)

Synchronous API

✅ (but not safe to call on UI thread)

Safe to call on UI thread

❌*

✅ (work is moved to Dispatchers.IO under the hood)

✅ (work is moved to Dispatchers.IO under the hood)

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

2. DataStore 的导入

目前 DataStore 还处于 alpha 版本,要使用 DataStore 需要添加的依赖如下:

  1. dependencies {
  2. // Preferences DataStore
  3. implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"
  4. // Proto DataStore
  5. implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
  6. }

3. DataStore 的使用

DataStore 分为 Preferences DataStore 和 Proto DataStore。

3.1 Preferences DataStore

Preferences DataStore 是由类 DataStore 和 Preferences 实现,用于存储简单的键值对到磁盘。

3.1.1 Preferences DataStore 的创建

  1. private val DATASTORE_PREFERENCE_NAME = "DataStorePreference"//定义 DataStore 的名字
  2. mDataStorePre = this.createDataStore(
  3. name = DATASTORE_PREFERENCE_NAME
  4. )

 createDataStore 是 Context 的一个扩展方法:

  1. fun Context.createDataStore(
  2. name: String,
  3. corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
  4. migrations: List<DataMigration<Preferences>> = listOf(),
  5. scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
  6. ): DataStore<Preferences> =
  7. PreferenceDataStoreFactory.create(
  8. produceFile = {
  9. File(this.filesDir, "datastore/$name.preferences_pb")
  10. },
  11. corruptionHandler = corruptionHandler,
  12. migrations = migrations,
  13. scope = scope
  14. )

3.1.2 数据的写入和读取

  1. private suspend fun savePreInfo(value: String) {
  2. var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
  3. mDataStorePre.edit { mutablePreferences ->
  4. mutablePreferences[preKey] = value
  5. }
  6. }
  7. private suspend fun readPreInfo(): String {
  8. var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
  9. var value = mDataStorePre.data.map { preferences ->
  10. preferences[preKey] ?: ""
  11. }
  12. return value.first()
  13. }

Preferences DataStore 以键值对的形式存储在本地,首先应该定义一个 Key:

 var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)

key 的类型是 Preferences.Key<T>,但是只支持 Int , String, Boolean , Float , Long 类型:

  1. inline fun <reified T : Any> preferencesKey(name: String): Preferences.Key<T> {
  2. return when (T::class) {
  3. Int::class -> {
  4. Preferences.Key<T>(name)
  5. }
  6. String::class -> {
  7. Preferences.Key<T>(name)
  8. }
  9. Boolean::class -> {
  10. Preferences.Key<T>(name)
  11. }
  12. Float::class -> {
  13. Preferences.Key<T>(name)
  14. }
  15. Long::class -> {
  16. Preferences.Key<T>(name)
  17. }
  18. Set::class -> {
  19. throw IllegalArgumentException("Use `preferencesSetKey` to create keys for Sets.")
  20. }
  21. else -> {
  22. throw IllegalArgumentException("Type not supported: ${T::class.java}")
  23. }
  24. }
  25. }

Preferences DataStore 中是通过 DataStore.edit() 写入数据,edit 方法是个 suspend 函数,必须在协程中进行调用;通过 DataStore.data 去读取数据,返回的是一个 Flow<T> 。

具体的使用例子如下:

  1. R.id.btn_pre_save -> {
  2. var textPre = edit_pre.text.trim().toString()
  3. lifecycleScope.launch {
  4. savePreInfo(textPre)
  5. }
  6. }
  7. R.id.btn_pre_read -> {
  8. lifecycleScope.launch {
  9. var value = readPreInfo()
  10. Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
  11. }
  12. }

3.1.3 从 SharedPreference 迁移数据

Google 推出 DataStore 的目的是为了取代 SharedPreference,对于老项目,就需要从 SharedPreference 中进行数据的迁移,从 SharedPreference 迁移到 DataStore。

3.1.1 中 createDataStore 方法的参数中有 migrations 参数:

migrations: List<DataMigration<Preferences>> = listOf()

只需要在 createDataStore 方法中按照如下格式就可以自动完成数据的迁移:

  1. mDataStorePre = this.createDataStore(
  2. name = DATASTORE_PREFERENCE_NAME,
  3. migrations = listOf(SharedPreferencesMigration(this, SP_PREFERENCE_NAME))
  4. )

3.2 Proto DataStore 

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倍

  • 减少了二义性

  • 生成了更容易在编程中使用的数据访问类

3.2.1 Proto DataStore 的创建

在创建 Proto DataStore 的时候,在 AndroidStudio 中,必须先做如下配置:

在 project 的 build.gradle 中添加依赖:

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'

在 app 的 build.gradle 中,修改的比较多,所以整个文件都贴出来:

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'kotlin-android'
  3. apply plugin: 'kotlin-android-extensions'
  4. apply plugin: 'com.google.protobuf'
  5. android {
  6. compileSdkVersion 30
  7. buildToolsVersion "30.0.2"
  8. defaultConfig {
  9. applicationId "cn.zzw.datastore"
  10. minSdkVersion 21
  11. targetSdkVersion 30
  12. versionCode 1
  13. versionName "1.0"
  14. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  15. }
  16. buildTypes {
  17. release {
  18. minifyEnabled false
  19. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  20. }
  21. }
  22. buildFeatures {
  23. dataBinding true
  24. }
  25. kotlinOptions {
  26. jvmTarget = "1.8"
  27. }
  28. sourceSets {
  29. main {
  30. proto {
  31. srcDir 'src/main/proto'
  32. include '**/*.proto'
  33. }
  34. }
  35. }
  36. }
  37. dependencies {
  38. implementation fileTree(dir: "libs", include: ["*.jar"])
  39. implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
  40. implementation 'androidx.core:core-ktx:1.3.2'
  41. implementation 'androidx.appcompat:appcompat:1.2.0'
  42. implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
  43. testImplementation 'junit:junit:4.12'
  44. androidTestImplementation 'androidx.test.ext:junit:1.1.2'
  45. androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
  46. // Preferences DataStore
  47. implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"
  48. // Proto DataStore
  49. implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
  50. implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
  51. implementation "com.google.protobuf:protobuf-javalite:3.10.0"
  52. }
  53. protobuf {
  54. protoc {
  55. artifact = "com.google.protobuf:protoc:3.10.0"
  56. }
  57. // Generates the java Protobuf-lite code for the Protobufs in this project. See
  58. // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
  59. // for more information.
  60. generateProtoTasks {
  61. all().each { task ->
  62. task.builtins {
  63. java {
  64. option 'lite'
  65. }
  66. }
  67. }
  68. }
  69. }

接着,在目录 app/src/main/proto 创建文件 user_prefs.proto:

  1. syntax = "proto3";
  2. option java_package = "cn.zzw.datastore";
  3. option java_multiple_files = true;
  4. message UserPreferences {
  5. int32 id = 1;
  6. string name = 2;
  7. int32 age = 3;
  8. string phone = 4;
  9. }

记得要执行 rebuild project 。

接着创建 UserPreferencesSerializer:

  1. package cn.zzw.datastore
  2. import androidx.datastore.Serializer
  3. import java.io.InputStream
  4. import java.io.OutputStream
  5. object UserPreferencesSerializer : Serializer<UserPreferences> {
  6. override fun readFrom(input: InputStream): UserPreferences {
  7. return UserPreferences.parseFrom(input)
  8. }
  9. override fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
  10. }

最后创建 Proto DataStore :

  1. mDataStorePro =
  2. this.createDataStore(
  3. fileName = "user_pros.pb",
  4. serializer = UserPreferencesSerializer
  5. )

3.2.2 数据的写入和读取

  1. private suspend fun saveProInfo(value: String) {
  2. mDataStorePro.updateData { preferences ->
  3. preferences.toBuilder().setId(110).setName(value).setAge(39).setPhone("119120").build()
  4. }
  5. }
  6. private suspend fun readProInfo(): String {+
  7. val userPreferencesFlow: Flow<UserPreferences> = mDataStorePro.data
  8. return userPreferencesFlow.first().toString()
  9. }

调用如下:

  1. R.id.btn_pro_save -> {
  2. var textPre = edit_pro.text.trim().toString()
  3. lifecycleScope.launch {
  4. saveProInfo(textPre)
  5. }
  6. }
  7. R.id.btn_pro_read -> {
  8. lifecycleScope.launch {
  9. var value = readProInfo()
  10. Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
  11. }
  12. }

4.总结

目前 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

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/309345
推荐阅读
相关标签
  

闽ICP备14008679号