一、DataStore簡介
Jetpack DataStore 是一種數(shù)據(jù)存儲解決方案,允許您使用協(xié)議緩沖區(qū)存儲鍵值對或類型化對象。DataStore 使用 Kotlin 協(xié)程和 Flow 以異步、一致的事務(wù)方式存儲數(shù)據(jù)。
Preferences DataStore 和 Proto DataStore
DataStore 提供兩種不同的實現(xiàn):Preferences DataStore 和 Proto DataStore。
- Preferences DataStore 使用鍵存儲和訪問數(shù)據(jù)。此實現(xiàn)不需要預(yù)定義的架構(gòu),也不確保類型安全。
- Proto DataStore 將數(shù)據(jù)作為自定義數(shù)據(jù)類型的實例進行存儲。此實現(xiàn)要求您使用協(xié)議緩沖區(qū)來定義架構(gòu),但可以確保類型安全。
二、DataStore使用
1、添加依賴
Preferences DataStore
// Preferences DataStore (SharedPreferences like APIs)
dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
// optional - RxJava2 support
implementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")
// optional - RxJava3 support
implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
implementation("androidx.datastore:datastore-preferences-core:1.0.0")
}
Proto DataStore
// Typed DataStore (Typed API surface, such as Proto)
dependencies {
implementation("androidx.datastore:datastore:1.0.0")
// optional - RxJava2 support
implementation("androidx.datastore:datastore-rxjava2:1.0.0")
// optional - RxJava3 support
implementation("androidx.datastore:datastore-rxjava3:1.0.0")
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
implementation("androidx.datastore:datastore-core:1.0.0")
}
注意:如果您將
datastore-preferences-core工件與 Proguard 搭配使用,就必須手動將 Proguard 規(guī)則添加到proguard-rules.pro文件中,以免您的字段遭到刪除。
2、使用 Preferences DataStore 存儲鍵值對
Preferences DataStore 實現(xiàn)使用DataStore和Preferences類將簡單的鍵值對保留在磁盤上。
1.創(chuàng)建 Preferences DataStore
使用由preferencesDataStore創(chuàng)建的屬性委托來創(chuàng)建 Datastore<Preferences> 實例。在您的 Kotlin 文件頂層調(diào)用該實例一次,便可在應(yīng)用的所有其余部分通過此屬性訪問該實例。這樣可以更輕松地將 DataStore 保留為單例。此外,如果您使用的是 RxJava,請使用RxPreferenceDataStoreBuilder。必需的 name 參數(shù)是 Preferences DataStore 的名稱。
RxDataStore<Preferences> dataStore =new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();
2.從 Preferences DataStore 讀取內(nèi)容
由于 Preferences DataStore 不使用預(yù)定義的架構(gòu),因此您必須使用相應(yīng)的鍵類型函數(shù)為需要存儲在 DataStore<Preferences> 實例中的每個值定義一個鍵。例如,如需為 int 值定義一個鍵,請使用intPreferencesKey()。然后,使用DataStore.data屬性,通過 Flow 提供適當(dāng)?shù)拇鎯χ怠?/p>
Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");
Flowable<Integer> exampleCounterFlow =dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));
Key的類型
Preferences.Key<Int> intPreferencesKey
Preferences.Key<Double> doublePreferencesKey
Preferences.Key<String> stringPreferencesKey
Preferences.Key<Boolean> booleanPreferencesKey
Preferences.Key<Float> floatPreferencesKey
Preferences.Key<Long> longPreferencesKey
Preferences.Key<Set<String>> stringSetPreferencesKey
3.將內(nèi)容寫入 Preferences DataStore
Preferences DataStore 提供了一個edit() 函數(shù),用于以事務(wù)方式更新 DataStore 中的數(shù)據(jù)。該函數(shù)的 transform 參數(shù)接受代碼塊,您可以在其中根據(jù)需要更新值。轉(zhuǎn)換塊中的所有代碼均被視為單個事務(wù)。
Single<Preferences> updateResult = dataStore.updateDataAsync(prefsIn -> {
MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
Integer currentInt = prefsIn.get(INTEGER_KEY);
mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
return Single.just(mutablePreferences);
});
3、使用 Proto DataStore 存儲類型化的對象
Proto DataStore 實現(xiàn)使用 DataStore 和協(xié)議緩沖區(qū)將類型化的對象保留在磁盤上。
1.定義架構(gòu)
Proto DataStore 要求在 app/src/main/proto/ 目錄的 proto 文件中保存預(yù)定義的架構(gòu)。此架構(gòu)用于定義您在 Proto DataStore 中保存的對象的類型。如需詳細(xì)了解如何定義 proto 架構(gòu),請參閱protobuf 語言指南。
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
注意:您的存儲對象的類在編譯時由 proto 文件中定義的 message 生成。請務(wù)必重新構(gòu)建您的項目。
2.創(chuàng)建 Proto DataStore
創(chuàng)建 Proto DataStore 來存儲類型化對象涉及兩個步驟:
定義一個實現(xiàn) Serializer<T> 的類,其中 T 是 proto 文件中定義的類型。此序列化器類會告知 DataStore 如何讀取和寫入您的數(shù)據(jù)類型。請務(wù)必為該序列化器添加默認(rèn)值,以便在尚未創(chuàng)建任何文件時使用。
使用由 dataStore 創(chuàng)建的屬性委托來創(chuàng)建 DataStore<T> 的實例,其中 T 是在 proto 文件中定義的類型。在您的 Kotlin 文件頂層調(diào)用該實例一次,便可在應(yīng)用的所有其余部分通過此屬性委托訪問該實例。filename 參數(shù)會告知 DataStore 使用哪個文件存儲數(shù)據(jù),而 serializer 參數(shù)會告知 DataStore 第 1 步中定義的序列化器類的名稱。
private static class SettingsSerializer implements Serializer<Settings> {
@Override
public Settings getDefaultValue() {
Settings.getDefaultInstance();
}
@Override
public Settings readFrom(@NotNull InputStream input) {
try {
return Settings.parseFrom(input);
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException(“Cannot read proto.”, exception);
}
}
@Override
public void writeTo(Settings t, @NotNull OutputStream output) {
t.writeTo(output);
}
}
RxDataStore<Byte> dataStore =
new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();
3.從 Proto DataStore 讀取內(nèi)容
使用 DataStore.data 顯示所存儲對象中相應(yīng)屬性的 Flow。
Flowable<Integer> exampleCounterFlow =
dataStore.data().map(settings -> settings.getExampleCounter());
4.將內(nèi)容寫入 Proto DataStore
Proto DataStore 提供了一個updateData() 函數(shù),用于以事務(wù)方式更新存儲的對象。updateData() 為您提供數(shù)據(jù)的當(dāng)前狀態(tài),作為數(shù)據(jù)類型的一個實例,并在原子讀-寫-修改操作中以事務(wù)方式更新數(shù)據(jù)。
Single<Settings> updateResult =
dataStore.updateDataAsync(currentSettings ->
Single.just(
currentSettings.toBuilder()
.setExampleCounter(currentSettings.getExampleCounter() + 1)
.build()));
4、在同步代碼中使用 DataStore
注意:請盡可能避免在 DataStore 數(shù)據(jù)讀取時阻塞線程。阻塞界面線程可能會導(dǎo)致ANR或界面卡頓,而阻塞其他線程可能會導(dǎo)致死鎖。
DataStore 的主要優(yōu)勢之一是異步 API,但可能不一定始終能將周圍的代碼更改為異步代碼。如果您使用的現(xiàn)有代碼庫采用同步磁盤 I/O,或者您的依賴項不提供異步 API,就可能出現(xiàn)這種情況。
Kotlin 協(xié)程提供runBlocking()協(xié)程構(gòu)建器,以幫助消除同步與異步代碼之間的差異。您可以使用 runBlocking() 從 DataStore 同步讀取數(shù)據(jù)。RxJava 在 Flowable 上提供阻塞方法。以下代碼會阻塞發(fā)起調(diào)用的線程,直到 DataStore 返回數(shù)據(jù):
Settings settings = dataStore.data().blockingFirst();
對界面線程執(zhí)行同步 I/O 操作可能會導(dǎo)致 ANR 或界面卡頓。您可以通過從 DataStore 異步預(yù)加載數(shù)據(jù)來減少這些問題:
dataStore.data().first().subscribe();
這樣,DataStore 可以異步讀取數(shù)據(jù)并將其緩存在內(nèi)存中。以后使用 runBlocking() 進行同步讀取的速度可能會更快,或者如果初始讀取已經(jīng)完成,可能也可以完全避免磁盤 I/O 操作。