Android开发拾遗:如何使用Proto DataStore
持久化一些用户数据是一个常见的需求,例如保存用户设置,Android提供了一个方便的机制,叫做DataStore。其中有两套API,一个是Preferences DataStore
,可以存取简单的key-value
数据;另一个是Proto DataStore
,顾名思义,这需要开发者定义一个protocol buffers的schema,可以存取自定义的数据类型,并提供类型安全保证。
最初我仅用到了简单的键值对API,因为业务上需要的配置项逐渐变多,并且在一些地方我需要使用枚举,所以萌生了从Preferences DataStore
转到Proto DataStore
的念头。但可惜的是,Android的文档关于Proto DataStore
没有详细的描述,在参考了一些开源代码后,我找到了能适配Kotlin
以及Gradle Kotlin DSL
的使用方法,在此记录一下。
Schema
首先要在app/src/main/proto
目录下创建一个protobuf文件,如settings.proto
:
syntax = "proto3"; // 这里替换成自己的包名 option java_package = "com.example.application"; option java_multiple_files = true; message Settings { int32 example_counter = 1; }
Gradle配置
有了schema之后,还需要有对应的用来序列化/反序列化的数据结构,这个数据结构可以通过库来生成。修改Gradle配置:
import com.google.protobuf.gradle.id plugins { // ... id("com.google.protobuf") version "0.9.1" } dependencies { // 添加这两个依赖 implementation("androidx.datastore:datastore:1.1.1") implementation("com.google.protobuf:protobuf-javalite:3.17.3") } // 这里Android Studio可能有lint报错,直接忽略,sync gradle protobuf { protoc { artifact = "com.google.protobuf:protoc:3.21.9" } generateProtoTasks { all().forEach { task -> task.builtins { id("java") { option("lite") } } } } }
在代码中使用
先创建一个SettingsSerializer.kt
文件,定义序列化器:
// 这个Settings类是自动生成的 object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings.getDefaultInstance() override suspend fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo( t: Settings, output: OutputStream) = t.writeTo(output) } val Context.settingsDataStore: DataStore<Settings> by dataStore( fileName = "settings.pb", serializer = SettingsSerializer )
读取时就可以使用冷流:
val settings = context.settingsDataStore.data.first()
如果要设置值,可以使用updateData
方法:
suspend fun incrementCounter() { context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build() } }
EOF