持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情
ProtoDataStore
今天我们将会去了解一下DataStore
的另外一种实现ProtoDataStore
。ProtoDataStore
的实现是使用协议缓冲区(Proto Buffer )来只是类型化对象的存储,同时提供类型安全。他消除了对键值对使用的需要,使其和SharedPreference
和PreferenceDataStore
s实现不同。下面我们就看下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>
的类。我们需要覆盖三个方法defaultValue
:序列化器默认值,在尚未创建任何文件时使用。writeTo
:将数据对象转换为适合存储的格式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()
}
}
更新数据的操作是原子读 ---修改----写操作,并且是以事务的方式完成。这意味着数据处理操作的特定顺序(在此期间为其他线程锁定数据)保证了一致性并防止了竞争条件。只有在transform
和updateData
成功完成之后,数据才会持久地存储到磁盘和DataStore中。