JetPack--DataStore中ProtoDataStore的使用

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情

ProtoDataStore

今天我们将会去了解一下DataStore的另外一种实现ProtoDataStoreProtoDataStore的实现是使用协议缓冲区(Proto Buffer )来只是类型化对象的存储,同时提供类型安全。他消除了对键值对使用的需要,使其和SharedPreferencePreferenceDataStores实现不同。下面我们就看下ProtoDataStore的使用。

Proto Buffer

ProtoBuffers是一种用于序列化结构数据的一种语言,平台无关的机制。它比XML更快、更小、更简单,并且相比于其他类型的数据更易于阅读

我们在项目中如果需要支持.proto结尾的文件,需要添加以下依赖(gradle 5.6以上 以及jdk 8)

1、Project中的build.gradle文件新增如

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.14'
  }
}

2.在app中的gradle文件中应用插件

apply plugin: 'com.google.protobuf'

3、指定.proto文件的目录位置


android{
    sourceSets {
        main {
            proto {
               //指定你proto文件位置
                srcDir 'src/main/proto'
            }
        }
    }
}

4、DataStore、proto的配置

dependencies {
    implementation  "androidx.datastore:datastore:1.0.0"
    implementation  "com.google.protobuf:protobuf-javalite:3.18.0"
    ...
}
protobuf {
    // 配置Protobuf编译和协议可执行文件
    protoc {
        // 从仓库下载
        artifact = "com.google.protobuf:protoc:3.14.0"
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                //配置输出类型
                java {
                    option 'lite'
                }
            }
        }
    }
 }

定义.proto文件,具体的协议格式可以按照ProtoBuf语言进行定义

syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;

message UserPreferences {

    bool isCheckRemeber = 1;
    LoginState loginState = 2;
    enum LoginState {
        DEFAULT = 0;
        LOGINING = 1;
        SUCCESS = 2;
        FAIL = 3;
    }
}

当完成proto文件的定义之后我们可以通过build生成对应的Java文件,我们可以在编译之后生成的目录下(build\generated\source\proto)找到它。我们每次更改.proto文件之后我们都需要rebuild一次

ProtoDataStore的使用

创建ProtoDataStore存储类型化数据需要两个步骤
  • 定义一个实现Serializer<T>的类。我们需要覆盖三个方法

    1. defaultValue:序列化器默认值,在尚未创建任何文件时使用。
    2. writeTo :将数据对象转换为适合存储的格式
    3. readFrom:将存储的数据格式转换为数据对象
    object ConfigSerializer : Serializer<Config> {
        
    override val defaultValue: Config = Config.getDefaultInstance()
    
        override suspend fun readFrom(input: InputStream): UserPreferences {
            try {
                return Config.parseFrom(input)
            } catch (exception: InvalidProtocolBufferException) {
                throw CorruptionException("Cannot read proto.", exception)
            }
        }
    
        override suspend fun writeTo(t: Config, output: OutputStream) =
            t.writeTo(output)
    }
    
  • 使用由 dataStore 创建的属性委托来创建 DataStore<T> 的实例,其中 T 是在 proto 文件中定义的类型。

    val Context.configDataStore: DataStore<Config> by dataStore(
      fileName = "config.pb",
      serializer = ConfigSerializer
    )
    

一般我们在Kotlin的顶层文件中进行protoDataStore的创建,以便我们在整个应用程序使用

数据的读取

使用DataStore.data来访问存储的数据,我们从当中进行数据时,流始终会返回一个数据或者是抛出异常

val exampleCounterFlow: Flow<Int> = context.configsDataStore.data
.catch { exception ->
      if (exception is IOException) {
        Log.e(TAG, "Error reading preferences.", exception)
        emit(defaultConfig())
      } else {
        throw exception
      }
 }
.map { config ->
    config.isCheckRemeber
 }
数据的写入

对于数据写入,我们将使用DataStore<UserPreferences>.updateData(transform: suspend (t: T) -> T)函数

suspend fun updateConfig(config){
    context.configDataStore.updateData{config ->
        config.toBuilder()
            .setIsCheckRemeber(config.isCheckRemeber)
            .build()
    }
}

更新数据的操作是原子读 ---修改----写操作,并且是以事务的方式完成。这意味着数据处理操作的特定顺序(在此期间为其他线程锁定数据)保证了一致性并防止了竞争条件。只有在transformupdateData成功完成之后,数据才会持久地存储到磁盘和DataStore中。

猜你喜欢

转载自juejin.im/post/7112688979446611981
今日推荐