JetPack--DataStore中ProtoDataStore的使用

語言: CN / TW / HK

theme: cyanosis highlight: androidstudio


持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第25天,點選檢視活動詳情

ProtoDataStore

今天我們將會去了解一下DataStore的另外一種實現ProtoDataStoreProtoDataStore的實現是使用協議緩衝區(Proto Buffer )來只是型別化物件的儲存,同時提供型別安全。他消除了對鍵值對使用的需要,使其和SharedPreferencePreferenceDataStores實現不同。下面我們就看下ProtoDataStore的使用。

Proto Buffer

ProtoBuffers是一種用於序列化結構資料的一種語言,平臺無關的機制。它比XML更快、更小、更簡單,並且相比於其他型別的資料更易於閱讀

我們在專案中如果需要支援.proto結尾的檔案,需要新增以下依賴(gradle 5.6以上 以及jdk 8)

1、Project中的build.gradle檔案新增如

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

2.在app中的gradle檔案中應用外掛

gradle apply plugin: 'com.google.protobuf'

3、指定.proto檔案的目錄位置

```gradle

android{    sourceSets {        main {            proto {   //指定你proto檔案位置                srcDir 'src/main/proto'           }       }   } } ```

4、DataStore、proto的配置

gradle 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語言進行定義

```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:將儲存的資料格式轉換為資料物件

    ```kotlin object ConfigSerializer : Serializer {     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 檔案中定義的型別。

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

一般我們在Kotlin的頂層檔案中進行protoDataStore的建立,以便我們在整個應用程式使用

資料的讀取

使用DataStore.data來訪問儲存的資料,我們從當中進行資料時,流始終會返回一個數據或者是丟擲異常

kotlin 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)函式

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

更新資料的操作是原子讀 ---修改----寫操作,並且是以事務的方式完成。這意味著資料處理操作的特定順序(在此期間為其他執行緒鎖定資料)保證了一致性並防止了競爭條件。只有在transformupdateData成功完成之後,資料才會持久地儲存到磁碟和DataStore中。