天天看点

Android Jetpack 之 DataStore 初探

前言

DataStore是google官方提供的,用于替换SharedPreferences来对简单数据进行存储的解决方案。对于SharedPreferences存在的缺陷

进行修补,并且可以和Kotlin协程与Flow结合。

DataStore与SharedPreferences 功能对比
Android Jetpack 之 DataStore 初探
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"
 
 }      
PreferencesDataStore 初始化
//user_preferences 生成的文件名称 user_preferences.preferences_pb
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(USER_PREFERENCES_NAME)      

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")
        }
    }      

读取

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

    }      

生成的文件内部结构 user_preferences.preferences_pb

Android Jetpack 之 DataStore 初探
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() //提交修改
    }      

文件内部结构对比。左边为SharedPreferences存储,右边为PreferencesDataStore 存储。

Android Jetpack 之 DataStore 初探
Android Jetpack 之 DataStore 初探

相同写入相同内容,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))
    }
)      

重新编译运行项目后检查 设备路径: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"
}      

//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")
}      
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;
}      

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

//创建序列化器

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)
    }
}      
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()
 }      
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
            }
        })
    })      
总结: