·  阅读

DataStore是Jetpack中新增的数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和 Flow 构建,以异步、一致的事务方式存储数据,并提供了以下两种不同的实现:

  • Preferences DataStore ,用于存储 键值对
  • Proto DataStore ,用于存储 类型化对象 (Protocol Buffer)
  • DataStore和MMKV、数据库的选择

    如果要处理键值对存储,已经在用MMKV的推荐继续使用,如果没有接入MMKV,那么也推荐接入MMKV,毕竟性能好效率高。

    或者使用Preferences DataStore,作为谷歌官方推出的SharedPreferences键值对存储替代方案,不过本文不多做描述。

    对于PB协议数据本地化存储,MMKV除了常规基础类型,只支持实现了Parcelable的对象存储,Protocol Buffer的对象要进行存储并没有现成方案,而Proto DataStore正好可以解决这个问题。

    对于性能,键值对存储DataStore的效率一定是比MMKV差的,但Proto DataStore存储PB对象的场景和MMKV没有什么可比性,所以不需过于关注此问题,优先考虑实现方便程度(比如类型检查问题,Proto DataStore可以在编译期显示错误提示,相比MMKV、SP等运行时崩溃,编译时错误提示更好让开发人员发现问题)。

    所以在项目中,根据不同场景选用不同存储方式:

  • 常规键值对存储——MMKV
  • 非大量数据的PB对象——Proto DataStore
  • 大量复杂关系数据——数据库
  • Protobuf 协议前后版本兼容问题

    协议缓存版本为旧版,最新代码+服务器版本为新版,存在以下情况:

  • 新版协议增加参数,使用缓存读取新参数,将会返回该参数类型对应的默认值
  • 新版协议删除已有参数,则新版代码也应该移除了对该参数的使用,否则报错
  • 仅修改参数名,则新版代码也应该修改了该参数的获取方法名,否则报错找不到该属性 从缓存数据中仍能被读取出来(因为protobuf协议中,每个参数都对应一个编号protoDataStore实质上是根据序号进行存储的,即修改序号对应的参数名,该项的数据仍然能读取到)

    修改参数类型,则新版代码也应该修改了该参数的使用类型,否则可能报错类型转换错误 从缓存中读取到的为该类型的默认值

    可以看出,Proto DataStore遵循了Protobuf协议向前向后兼容的特性:对于新增或修改类型的参数(服务端一般不会修改已有参数的类型),从缓存中读取并不会引起错误崩溃,只需要我们处理好返回默认值的情况;而对于仅修改了名称的参数,甚至还能从缓存中读取到原先名字对应保存的数据。

    这些特性都是专为存储Protobuf Buffer而存在的Proto DataStore所独有的,如果使用其他方案,比如将对象转为Byte存入MMVK,在取出并反序列化为对象后,还需要自行处理新旧对象间的差异,将是一件非常麻烦的事情。

    接入和封装使用

    引入项目gradle:

    api('androidx.datastore:datastore:1.0.0-beta01') {
        exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core'
        exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
    api('androidx.datastore:datastore-core:1.0.0-beta01') {
        exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
    

    最新版本为1.0.0正式版,这里我选择了适用于自己项目内无需升级其它组件的版本为1.0.0-beta01,核心api都在 datastore-core库中,datastore主要增加了一些扩展方法,比如快速创建全局单一实例的dataStore对象,避免多个对象操作同一文件的问题。

    常规使用方法:

  • 创建全局单一实例DataStore对象
  • val Context.userInfoStore: DataStore<UserProto> by dataStore(
        fileName = "userInfo.pb",
        serializer = UserProtoSerializer
    //创建序列化器
    object UserProtoSerializer : Serializer<UserProto> {
        override suspend fun readFrom(input: InputStream): UserProto {
            try {
                return UserProto.parseFrom(input)
            } catch (exception: InvalidProtocolBufferException) {
                throw CorruptionException("Cannot read proto.", exception)
        override suspend fun writeTo(t: UserProto, output: OutputStream) {
            t.writeTo(output)
        override val defaultValue: UserProto
            get() = UserProto.getDefaultInstance()
    
  • 写入和读取数据
  • private fun dataStoreProto() {
        //将内容写入 Proto DataStore
        lifecycleScope.launch {
            context.userInfoStore.updateData {
                it.toBuilder()
                    .setName("newName")
                    .setAge(18)
                    .build()
        //读取数据
        lifecycleScope.launch {
            val user = userInfoStore.data.first()
    

    封装后使用方法

    封装后,只需在项目中DataStoreManager中声明要保存的DataStore实例和对象类型,比如此处我保存了个人信息和设置信息的协议对象数据:

    object DataStoreManager {
        //退出登录时清空缓存
        fun clearAll() {
            GlobalScope.launch {
                dsMineInfo.update { it.toBuilder().clear().build() }
                dsSettingInfo.update { it.toBuilder().clear().build() }
        val dsMineInfo by lazy { ApplicationContext.getContext().dsMineInfo }
        val dsSettingInfo by lazy { ApplicationContext.getContext().dsSettingInfo }
     * 个人信息
    private val Context.dsMineInfo by dataStore(
        fileName = MineInfoProto::class.simpleName.toString(),
        serializer = getSerializer(
            defaultValue = MineInfoProto.getDefaultInstance(),
            readFrom = { MineInfoProto.parseFrom(it) })
     * 设置信息
    private val Context.dsSettingInfo by dataStore(
        fileName = SettingInfoProto::class.simpleName.toString(),
        serializer = getSerializer(
            defaultValue = SettingInfoProto.getDefaultInstance(),
            readFrom = { SettingInfoProto.parseFrom(it) })
    

    在业务场景对应处使用扩展方法读取数据,此处也可以使用Flow关联LiveData

    override fun onCreate() {
        super.onCreate()
        GlobalScope.launch(Dispatchers.IO) {
            val data = DataStoreManager.dsMineInfo.getValue()
            //使用数据
    

    在ViewModel中保存数据:

    val data = //获取服务器协议数据
    viewModel.viewModelScope.saveDataStore(DataStoreManager.dsMainInfo, data)
    viewModel.viewModelScope.launch {
        DataStoreManager.dsMainInfo.save(data)
    

    具体封装方法:

    * 保存数据 fun <T : GeneratedMessageLite> CoroutineScope.saveDataStore( dataStore: DataStore<T>?, data: T?, block: ((Boolean) -> Unit)? = null this.launch { dataStore.save(data, block) suspend fun <T : GeneratedMessageLite> DataStore<T>?.save( data: T?, block: ((Boolean) -> Unit)? = null this?.let { dataStore -> withContext(Dispatchers.IO) { try { val result = data?.let { dataStore.updateData { data } } ?: false block?.let { withContext(Dispatchers.Main) { it(result) } } catch (e: Exception) { Logz.tag("DataStoreUtil").e(e.message) block?.let { withContext(Dispatchers.Main) { it(false) } * 更新数据 suspend fun <T : GeneratedMessageLite> DataStore<T>?.update( block: ((Boolean) -> Unit)? = null, update: ((T) -> T) this?.let { dataStore -> withContext(Dispatchers.IO) { try { dataStore.updateData { update(it) block?.let { withContext(Dispatchers.Main) { it(true) } } catch (e: Exception) { Logz.tag("DataStoreUtil").e(e.message) block?.let { withContext(Dispatchers.Main) { it(false) } * 读取数据 suspend fun <T : GeneratedMessageLite> DataStore<T>?.getValue(): T? { return this?.let { dataStore -> withContext(Dispatchers.IO) { try { val data = dataStore.data.first() withContext(Dispatchers.Main) { data } } catch (e: Exception) { Logz.tag("DataStoreUtil").e(e.message) withContext(Dispatchers.Main) { null } * 读取Flow数据 inline fun <reified T : GeneratedMessageLite> DataStore<T>?.getValueAsFlow(): Flow<T>? { return this?.let { dataStore -> dataStore.data.catch { exception -> if (exception is IOException) { emit(T::class.java.newInstance()) } else { throw exception * 创建Protocol Buffer对应的Serializer internal fun <T : GeneratedMessageLite> getSerializer( defaultValue: T, readFrom: (input: InputStream) -> T ): Serializer<T> { return object : Serializer<T> { override val defaultValue: T get() = defaultValue override suspend fun readFrom(input: InputStream): T { try { return readFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) override suspend fun writeTo(t: T, output: OutputStream) { t.writeTo(output) 复制代码
    分类:
    Android