当前位置:   article > 正文

Android Jetpack 之 DataStore 初探_androidx.datastore

androidx.datastore
前言

DataStore是google官方提供的,用于替换SharedPreferences来对简单数据进行存储的解决方案。对于SharedPreferences存在的缺陷
进行修补,并且可以和Kotlin协程与Flow结合。

DataStore与SharedPreferences 功能对比

在这里插入图片描述

DataStore 功能

DataStore提供对基本类型和对象类型进行分开存储,分别使用 PreferencesDataStore,ProtoDataStore.通过名字很容区分 PreferencesDataStore用来存储基本类型数据,String、int、long、double、float、boolean、set< String >。
Proto DataStore 使用协议缓冲区来定义架构。使用协议缓冲区可持久保留强类型数据。与 XML 和其他类似的数据格式相比,协议缓冲区速度更快、规格更小、使用更简单,并且更清楚明了。虽然使用 Proto DataStore 需要学习新的序列化机制,但 Proto DataStore 有着强大的类型优势,值得学习。
并且提供了方便的迁移接口,对于基础类型存储可以通过配置自动实现迁移。

PreferencesDataStore 配置依赖

dependencies {
 //preferencesDatastore功能依赖
 implementation "androidx.datastore:datastore-preferences:1.0.0"
 //protoDatastore功能依赖
 implementation  "androidx.datastore:datastore-core:1.0.0"
 implementation  "com.google.protobuf:protobuf-javalite:3.18.0"
 implementation  "androidx.datastore:datastore:1.0.0"
 
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
PreferencesDataStore 初始化
//user_preferences 生成的文件名称 user_preferences.preferences_pb
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(USER_PREFERENCES_NAME)
  • 1
  • 2
  • 3

PreferencesDataStore 读取写入:需要放在协程中进行读写

    private suspend fun spStoreSave() {
        dataStore.edit { preferences ->
            preferences[stringPreferencesKey("one1")] = "中华人民共和国"
            preferences[intPreferencesKey("one2")] = 1
            preferences[doublePreferencesKey("one3")] = 1.00
            preferences[floatPreferencesKey("one4")] = 1f
            preferences[longPreferencesKey("one5")] = 1L
            preferences[booleanPreferencesKey("one6")] = true
            preferences[stringSetPreferencesKey("one7")] = setOf("1", "2", "3", "4", "5", "6")
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

读取

    private suspend fun getSpStore() {
        val flow = dataStore.data.catch { exception ->
            // dataStore.data throws an IOException when an error is encountered when reading data
            if (exception is IOException) {
                Log.e(TAG, "Error reading preferences.", exception)
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }.map { preferences ->
            println(preferences[stringPreferencesKey("one1")])
            println(preferences[intPreferencesKey("one2")])
            println(preferences[doublePreferencesKey("one3")])
            println(preferences[floatPreferencesKey("one4")])
            println(preferences[longPreferencesKey("one5")])
            println(preferences[booleanPreferencesKey("one6")])
            preferences[stringSetPreferencesKey("one7")]

        }
        flow.collect()

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

生成的文件内部结构 user_preferences.preferences_pb
在这里插入图片描述

PreferencesDataStore 与 SharedPreferences对比:大小、内部结构

SharedPreferences写入相同的数据后进行对比

    private fun saveUserInfo() {
        val userInfo = getSharedPreferences(SETTING, MODE_PRIVATE)
        val editor = userInfo.edit() //获取Editor
        editor.apply {
            putString("one1", "中华人民共和国")
            putInt("one2", 1)
            putFloat("one3",1.00f)
            putFloat("one4",1f)
            putLong("one5",1L)
            putBoolean("one6",true)
            putStringSet("one7", setOf("1", "2", "3", "4", "5", "6"))
        }
        editor.commit() //提交修改
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

文件内部结构对比。左边为SharedPreferences存储,右边为PreferencesDataStore 存储。
![在这里插入图片描述](https://img-blog.csdnimg.cn/249f02f525b0496e938fb82f74e3d5fa.png

在这里插入图片描述
相同写入相同内容,SharedPreferences比PreferencesDataStore存储大小多了3倍。

SharedPreferences迁移PreferencesDataStore

需要指定要迁入的 PreferencesDataStore 文件名称和要迁出的文件SharedPreferences文件名称,如下:

//SharedPreferences 旧存储结构SharedPreferences
private const val  SETTING = "setting"
//要迁入的新存储结构 PreferencesDataStore 
private const val USER_PREFERENCES_NAME = "user_preferences"

private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME,
    produceMigrations = { context ->
        // Since we're migrating from SharedPreferences, add a migration based on the
        // SharedPreferences name
        listOf(SharedPreferencesMigration(context, SETTING))
    }
)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

重新编译运行项目后检查 设备路径:data/data/packageName/files/datastore/ xx.preferences_pb文件,如果文件中有SharedPreferences内容,表示自动迁移完成了。

Proto DataStore

Proto DataStore 是用来存取可序列化对象,SharedPreferences 不能直接对对象进行存取,开发中 一般做法会将对象转换成jsonString进行存储,需要取出时再由取出来的jsonStrinng转换成需要的Bean文件。如果使用Proto DataStore存取那么可以直接取出定义的Bean对象。

1.添加protoBuffer 模板代码生成插件 和 Proto DataStore 依赖项

在项目app模块 build.gradle 文件中添加自动生成模板代码的插件和必要的依赖项

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id "com.google.protobuf" version "0.8.17"
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

//protobuf闭包 与 android闭包同一级

 protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.14.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'
                }
            }
        }
    }
}

dependencies {

    //protocol cache
    implementation  "androidx.datastore:datastore-core:1.0.0"
    implementation  "com.google.protobuf:protobuf-javalite:3.18.0"

    implementation("androidx.datastore:datastore:1.0.0")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
2.定义protobuf 对象并实现序列化(需要额外学习成本)

需要在**/app/src/main/proto**在建立一个后缀为.proto的 protoBuffer文件,比如这里创建一个名为
person.proto的文件。 之后需要手动进行编译,可以通过检查app/build/generrated/source/proto/debug/java/包名/下

syntax = "proto3";

// 默认生成路径 ${project.buildDir}/generated/source/proto
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;

message Person {
  //学号
  int32 id = 1;
  //姓名
  string name = 2;
  //年龄
  int32 age = 3;
  //班级
  string class = 4;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

创建一个序列化器,都是一样的写法,照着模板写就行

//创建序列化器

object PersonPreferencesSerializer : Serializer<Person> {

    override val defaultValue: Person = Person.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): Person {
        try {
            return Person.parseFrom(input)
        } catch (e: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", e)
        }
    }

    override suspend fun writeTo(t: Person, output: OutputStream) {
        t.writeTo(output)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
3.存取 protobuf 对象,并输出
 //存
 lifecycleScope.launch {
                persion.updateData { preferenceChanges ->
                    preferenceChanges.toBuilder()
                        .setId(10000)
                        .setAge(1)
                        .setName("小明")
                        .setClass_("一年级")
                        .build()
                }
            }
            
//取

       lifecycleScope.launch {
     persion.data.catch { exception ->
         // dataStore.data throws an IOException when an error is encountered when reading data
         if (exception is IOException) {
             Log.e(TAG, "Error reading sort order preferences.", exception)
             emit(Person.getDefaultInstance())
         } else {
             throw exception
         }
     }.map {
         with(it) {
             println("class =$class_ name =$name age =$age id =$id")
         }
     }.collect()
 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
proto DataStore 迁移

SharedPreferences 文件迁移到ProtoDataStore,需要手动取出来SharedPreferences内存储值,通过事务Build设置给Proto对象。需要注意的一点是每一个定义的proto 对象为一个单独的文件。如果SharedPreferences 内存储值需要转存入Proto对象中。需要在如下代码Listof(SharedPreferencesMigration(),SharedPreferencesMigration(), …)定义多个接收SharedPreferences值的对象。

private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(fileName = 对象.pb文件名称,
    serializer = UserPreferencesSerializer,
    produceMigrations = { context ->
        listOf(SharedPreferencesMigration(
            context, Sp文件名称
        ) { sharedPreferences: SharedPreferencesView, currentData: UserPreferences ->
            if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
                currentData.toBuilder().setSortOrder(
                    SortOrder.valueOf(
                        sharedPreferences.getString(
                            SORT_ORDER_KEY, SortOrder.NONE.name
                        )!!
                    )
                ).build()
            } else {
                currentData
            }
        })
    })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
总结:

对于简单数据存取使用SharedPreferences也有用MMKV替换SharedPreferences,Jetpack DataStore作为Google 官方推荐用来替换SharedPreferences的控件,在修复了SharedPreferences已知问题之外并提供了和当下流行的Kotlin 语言接口的api.在扩展方面有了保证。 用法上和SharedPreferences大同小异,具体使用SharedPreferences 还是DataStore,官方建议使用DataStore。

Google 官方proto-datastore 最佳实践
Google protocol-buffers 用法

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

闽ICP备14008679号