使用 Jetpack DataStore 進行數據存儲

做者 / Android 開發技術推廣工程師 Florina Muntenescu 與 Google 軟件工程師 Rohit Sathyanarayana

歡迎使用 Jetpack DataStore,這是一個通過改進的全新數據存儲解決方案,旨在替代原有的 SharedPreferences。Jetpack DataStore 基於 Kotlin 協程和 Flow 開發,並提供兩種不一樣的實現: Proto DataStorePreferences DataStore。其中 Proto DataStore,能夠存儲帶有類型的對象 (使用 protocol buffers 實現);Preferences DataStore,能夠存儲鍵值對。在 DataStore 中,數據以異步的、一致的、事務性的方式進行存儲,克服了 SharedPreferences 的大部分缺點。html

SharedPreferences 和 DataStore 對比

  • SharedPreferences 有一個看上去能夠在 UI 線程安全調用的同步 API,可是該 API 實際上執行了磁盤 I/O 操做。此外,apply() 方法會在 fsync() 阻塞 UI 線程。在您應用的任何地方,每當 Service 或 Activity 啓動或中止時,就會觸發等待 fsync() 的調用。由 apply() 安排的 fsync() 調用過程會阻塞 UI 線程,這也經常成爲形成 ANR 的源頭。
  • SharedPreferences 在分析出錯時會拋出運行時異常。

在兩種實現中,除非另外特指,不然 DataStore 會將首選項存儲在文件中,而且全部的數據操做都會在 Dispatchers.IO 上執行。java

雖然 Preferences DataStore 與 Proto DataStore 均可以存儲數據,但它們的實現方法不盡相同:android

  • Preference DataStore,就像 SharedPreferences 同樣,不能定義 schema 或保證以正確的類型訪問鍵值。
  • Proto DataStore 讓您能夠使用 Protocol buffers 定義 schema。使用 Protobufs 能夠保留強類型數據。它們相對於 XML 或其餘類似的數據格式要更快、更小、歧義更少。雖然 Proto DataStore 要求您學習一種新的序列化機制,但考慮到 Proto DataStore 所帶來的強類型 schema 的優點,咱們認爲這樣的代價是值得的。

Room 和 DataStore 對比

若是您有局部更新數據、參照完整性或支持大型、複雜數據集的需求,則應當考慮使用 Room 而不是 DataStore。DataStore 是小型、簡單數據集的理想選擇,它並不支持局部更新與參照完整性。git

使用 DataStore

首先添加 DataStore 依賴項。若是您使用的是 Proto DataStore,請確保您也添加了 proto 依賴項:github

def dataStoreVersion = "1.0.0-alpha05" 
// 在 Android 開發者網站上確認最新的版本號 
// https://developer.android.google.cn/jetpack/androidx/releases/datastore

// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:$dataStoreVersion"

// Proto DataStore
implementation  "androidx.datastore:datastore-core:$dataStoreVersion"

當您使用 Proto DataStore 時,您須要在 app/src/main/proto/ 目錄下使用 proto 文件定義您本身的 schema。有關定義 proto schema 的更多信息,請參閱 protobuf 語言指南安全

syntax = "proto3";

option java_package = "<your package name here>";
option java_multiple_files = true;

message Settings {
  int my_counter = 1;
}

建立 DataStore

您能夠使用 Context.createDataStore() 擴展方法建立 DataStore:app

// 建立 Preferences DataStore 
val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings"
)

若是您使用的是 Proto DataStore,您還須要實現 Serializer 接口來告訴 DataStore 如何讀取和寫入您的數據類型。異步

object SettingsSerializer : Serializer<Settings> {
    override fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
}

// 建立 Proto DataStore
val settingsDataStore: DataStore<Settings> = context.createDataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer
)

從 DataStore 讀取數據

不管是 Preferences 對象仍是您在 proto schema 中定義的對象,DataStore 都會以 Flow 的形式暴露已存儲的數據。DataStore 能夠確保在 Dispatchers.IO 上檢索數據,所以不會阻塞您的 UI 線程。ide

使用 Preferences DataStore:函數

val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
     .map { currentPreferences ->
        // 不一樣於 Proto DataStore,這裏不保證類型安全。
        currentPreferences[MY_COUNTER] ?: 0   
   }

使用 Proto DataStore:

val myCounterFlow: Flow<Int> = settingsDataStore.data
    .map { settings ->
        // myCounter 屬性由您的 proto schema 生成!
        settings.myCounter 
    }

向 DataStore 寫入數據

爲了寫入數據,DataStore 提供了一個 DataStore.updateData() 掛起函數,它會將當前存儲數據的狀態做爲參數提供給您,對於 Preferences 對象或是您在 proto schema 中定義的對象實例皆爲如此。updateData() 函數使用原子的讀、寫、修改操做並以事務的方式更新數據。當數據在磁盤上完成存儲時,此協程就會完成。

Preferences DataStore 還提供了一個 DataStore.edit() 函數來方便數據的更新。在此函數中,您會收到一個用於編輯的 MutablePreferences 對象,而不是 Preferences 對象。該函數與 updateData() 同樣,會在轉換代碼塊完成以後將修改應用到磁盤,而且當數據在磁盤上完成存儲時,此協程就會完成。

使用 Preferences DataStore:

suspend fun incrementCounter() {
    dataStore.edit { settings ->
        // 能夠安全地增長咱們的計數器,而不會由於資源競爭而丟失數據。
        val currentCounterValue = settings[MY_COUNTER] ?: 0
        settings[MY_COUNTER] = currentCounterValue + 1
    }
}

使用 Proto DataStore:

suspend fun incrementCounter() {
    settingsDataStore.updateData { currentSettings ->
        // 能夠安全地增長咱們的計數器,而不會由於資源競爭而丟失數據。
        currentSettings.toBuilder()
            .setMyCounter(currentSettings.myCounter + 1)
            .build()
    }
}

從 SharedPreferences 遷移至 DataStore

要從 SharedPreferences 遷移至 DataStore,您須要將 SharedPreferencesMigration 對象傳遞給 DataStore 構造器,DataStore 能夠自動完成從 SharedPreferences 遷移至 DataStore 的工做。遷移會在 DataStore 中發生任何數據訪問以前運行,這意味着在 DataStore.data 返回任何值以及 DataStore.updateData() 能夠更新數據以前,您的遷移必須已經成功。

若是您要遷移至 Preferences DataStore,您能夠使用 SharedPreferencesMigration 的默認實現。只須要傳入 SharedPreferences 構造時所使用的名字就能夠了。

使用 Preferences DataStore:

val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings",
    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)

當須要遷移至 Proto DataStore 時,您必須實現一個映射函數,用來定義如何將 SharedPreferences 所使用的鍵值對遷移到您所定義的 DataStore schema。

使用 Proto DataStore:

val settingsDataStore: DataStore<Settings> = context.createDataStore(
    produceFile = { File(context.filesDir, "settings.preferences_pb") },
    serializer = SettingsSerializer,
    migrations = listOf(
        SharedPreferencesMigration(
            context,
            "settings_preferences"            
        ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
            // 在這裏將 sharedPrefs 映射至您的類型。
          }
    )
)

總結

SharedPreferences 有着許多缺陷: 看起來能夠在 UI 線程安全調用的同步 API 其實並不安全、沒有提示錯誤的機制、缺乏事務 API 等等。DataStore 是 SharedPreferences 的替代方案,它解決了 Shared Preferences 的絕大部分問題。DataStore 包含使用 Kotlin 協程和 Flow 實現的徹底異步 API,能夠處理數據遷移、保證數據一致性,而且能夠處理數據損壞。

因爲 DataStore 仍處於測試階段,所以咱們須要您的幫助以使其變得更好!首先,您能夠經過咱們的 文檔 瞭解有關 DataStore 的更多信息,也能夠經過咱們爲您準備的兩個 Codelab: Preferences DataStore codelabProto DataStore codelab 來嘗試 DataStore。最後,您能夠在 問題跟蹤器 上建立問題,讓咱們知道如何來改進 DataStore。

相關文章
相關標籤/搜索