DataStore
Android Jetpack 的一部分。
Jetpack DataStore 是一項資料儲存解決方案,可讓您使用通訊協定緩衝區儲存關鍵值組合或輸入的物件。DataStore 使用 Kotlin 處理 coroutines 和 Flow,以非同步、一致的方式和交易方式儲存資料。
如果您目前使用 SharedPreferences
儲存資料,請考慮改移駕至 DataStore。
Preferences DataStore 和 Proto DataStore
DataStore 提供兩種不同的導入方式:Preferences DataStore 和 Proto DataStore。
- Preferences DataStore 會使用金鑰儲存及存取資料。這項實作不需要有預先定義的結構定義,且不提供類型安全性。
- Proto DataStore 會將資料儲存為自訂資料類型的例項。這個實作要求使用通訊協定緩衝區去定義其結構定義,但也提供類型安全性。
正確使用 DataStore
為了正確使用 DataStore,請務必遵守下列規則:
在同一個程序中,請勿為特定檔案建立多個
DataStore
例項。這麼做可能會導致所有 DataStore 功能失效。如果同一個程序中有多個 DataStore 對特定檔案有效,則 DataStore 會在讀取或更新資料時擲回IllegalStateException
。DataStore 的泛型類型
必須是不可變動的。 變更 DataStore 中使用的型別會使 DataStore 提供的任何保證失效,並造成可能嚴重且難以偵測的錯誤。強烈建議您使用通訊協定緩衝區,因為它可提供不變性保證、簡單的 API 和高效率的序列化。請勿在同一個檔案中混用
SingleProcessDataStore
和MultiProcessDataStore
。如果您想透過多個程序存取DataStore
,請務必使用MultiProcessDataStore
。
設定
如要在應用程式中使用 Jetpack DataStore,請根據想採用的實作方式,將以下內容新增到 Gradle 檔案:
Preferences DataStore
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.2" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.2" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.2" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.2" }
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.2") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.2") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.2") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.2") }
Proto DataStore
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.2" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.2" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.2" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.2" }
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.2") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.2") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.2") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.2") }
透過 Preferences DataStore 儲存鍵/值組合
Preferences DataStore 實作會使用 DataStore
和 Preferences
類別,將簡單的鍵/值組合保留在磁碟中。
建立 Preferences DataStore
請使用 preferencesDataStore
建立的屬性委派來建立 DataStore<Preferences>
的例項。請在 kotlin 檔案的頂層呼叫一次,然後在整個應用程式的其他資源中透過該資源存取該檔案。這能讓你更輕鬆地將 DataStore
視為單例模式。如果您使用 RxJava,請使用 RxPreferenceDataStoreBuilder
。必要的 name
參數是 Preferences DataStore 的名稱。
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
RxDataStore<Preferences> dataStore =
new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();
從 Preferences DataStore 讀取
由於 Preferences DataStore 不使用預先定義的結構定義,因此您必須使用對應的金鑰類型函式,為此您需要儲存在 DataStore<Preferences>
例項中的每個值定義金鑰。舉例來說,如要為整數值定義金鑰,請使用 intPreferencesKey()
。然後使用 DataStore.data
屬性並用 Flow
公開適當的儲存值。
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");
Flowable<Integer> exampleCounterFlow =
dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));
寫入 Preferences DataStore
Preferences DataStore 提供的 edit()
函式會更新 DataStore
中的資料。函式的 transform
參數可接受一段程式碼,可以視需要更新其值。轉換區塊中的所有程式碼都視為單一交易。
suspend fun incrementCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
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);
});
// The update is completed once updateResult is completed.
使用 Proto DataStore 儲存已輸入的物件
Proto DataStore 實作會使用 DataStore 和通訊協定緩衝區,將輸入的物件保留到磁碟。
定義結構定義
Proto DataStore 需要 app/src/main/proto/
目錄中原始檔案的已預先定義的結構定義。這個結構定義定義了您儲存在 Proto DataStore 中的物件類型。如要進一步瞭解如何定義 Proto 結構定義,請參閱 「protobuf 語言指南」。
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
建立 Proto Datastore
建立 Proto DataStore 以儲存已輸入的物件有兩個步驟:
- 定義實作
Serializer<T>
的類別,其中T
是 Proto 檔案中定義的類型。這個序列化器類別會通知 DataStore 如何讀取及寫入您的資料類型。請確認您尚未建立序列化器要使用的預設值,如果您還尚未建立任何檔案的話。 - 使用
dataStore
建立的屬性來建立DataStore<T>
的例項,其中T
是 proto 檔案中所定義的類型。請先在 kotlin 檔案的頂層呼叫此方法,然後在應用程式的其餘部分透過此屬性存取該檔案。filename
參數會告訴 DataStore 要使用哪個檔案儲存資料,而serializer
參數會指示 DataStore 在步驟 1 中定義的序列化器的名稱。
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
)
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();
從 Proto DataStore 讀取
使用 DataStore.data
即可從儲存的物件中顯示適當屬性的 Flow
。
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
.map { settings ->
// The exampleCounter property is generated from the proto schema.
settings.exampleCounter
}
Flowable<Integer> exampleCounterFlow =
dataStore.data().map(settings -> settings.getExampleCounter());
寫入 Proto DataStore
Proto DataStore 提供的 updateData()
函式會更新儲存的物件。updateData()
會以資料類型的形式提供資料目前的狀態,並在不可部分完成的讀/寫/改作業中更新資料。
suspend fun incrementCounter() {
context.settingsDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setExampleCounter(currentSettings.exampleCounter + 1)
.build()
}
}
Single<Settings> updateResult =
dataStore.updateDataAsync(currentSettings ->
Single.just(
currentSettings.toBuilder()
.setExampleCounter(currentSettings.getExampleCounter() + 1)
.build()));
在同步程式碼中使用 DataStore
DataStore 的主要優點之一就是非同步 API,但可能無法將周圍的程式碼變更為非同步。如果您使用的是使用同步磁碟 I/O 的現有程式碼集,或您沒有提供非同步 API 的依附元件,就可能會發生這種情況。
Kotlin coroutine 提供 runBlocking()
coroutine 建構工具,協助消除同步與非同步程式碼之間的差距。您可以使用 runBlocking()
同步讀取 DataStore 的資料。RxJava 已於 Flowable
提供封鎖方法。下列程式碼會封鎖呼叫執行緒,直到 DataStore 傳回資料為止:
val exampleData = runBlocking { context.dataStore.data.first() }
Settings settings = dataStore.data().blockingFirst();
對 UI 執行緒執行同步 I/O 作業可能會導致 ANR 或 UI 遭拒。如要避免這些問題,您可以非同步從 DataStore 預先載入資料:
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
context.dataStore.data.first()
// You should also handle IOExceptions here.
}
}
dataStore.data().first().subscribe();
這樣一來,DataStore 會以非同步方式讀取資料,並將資料快取放在記憶體中。日後使用 runBlocking()
執行同步讀取作業可能會更快,但如果初始讀取作業已完成,則可能避免磁碟 I/O 一起作業。
在多程序程式碼中使用 DataStore
您可以設定 DataStore,讓其在不同程序中存取相同資料,並提供與單一程序相同的資料一致性保證。具體來說,DataStore 保證:
- 讀取作業只會傳回已儲存至磁碟的資料。
- 寫入後的讀取一致性。
- 寫入作業會序列化。
- 讀取作業不會受到寫入作業的阻斷。
請考慮含有服務和活動的範例應用程式:
這項服務會在個別程序中執行,並定期更新 DataStore
<service
android:name=".MyService"
android:process=":my_process_id" />override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
scope.launch {
while(isActive) {
dataStore.updateData {
Settings(lastUpdate = System.currentTimeMillis())
}
delay(1000)
}
}
}應用程式會收集這些變更並更新 UI
val settings: Settings by dataStore.data.collectAsState()
Text(
text = "Last updated: $${settings.timestamp}",
)
如要在不同程序中使用 DataStore,您必須使用 MultiProcessDataStoreFactory
建構 DataStore 物件。
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
會通知 DataStore 如何讀取及寫入資料類型。請確認您尚未建立序列化器要使用的預設值,如果您還尚未建立任何檔案的話。以下是使用 kotlinx.serialization 的實作範例:
@Serializable
data class Settings(
val lastUpdate: Long
)
@Singleton
class SettingsSerializer @Inject constructor() : Serializer<Settings> {
override val defaultValue = Settings(lastUpdate = 0)
override suspend fun readFrom(input: InputStream): Timer =
try {
Json.decodeFromString(
Settings.serializer(), input.readBytes().decodeToString()
)
} catch (serialization: SerializationException) {
throw CorruptionException("Unable to read Settings", serialization)
}
override suspend fun writeTo(t: Settings, output: OutputStream) {
output.write(
Json.encodeToString(Settings.serializer(), t)
.encodeToByteArray()
)
}
}
您可以使用 Hilt 依附元件插入功能,確保每個程序的 DataStore 例項皆不重複:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
處理檔案毀損
在極少數情況下,DataStore 的磁碟上永久檔案可能會毀損。根據預設,DataStore 不會自動復原損毀的資料,嘗試讀取資料時,系統會擲回 CorruptionException
。
DataStore 提供損毀處理程序 API,可協助您在這種情況下順利復原,並避免擲回例外狀況。設定後,損毀處理程序會將損毀的檔案替換為包含預先定義的預設值的新檔案。
如要設定這個處理程序,請在 by dataStore()
或 DataStoreFactory
工廠方法中建立 DataStore 例項時,提供 corruptionHandler
:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
提供意見
歡迎透過下列資源與我們分享意見和想法:
- Issue Tracker
- 報告問題,幫助我們修正錯誤。
其他資源
如要進一步瞭解 Jetpack DataStore,請參閱下列其他資源:
範例
網誌
程式碼研究室
目前沒有任何建議。
建議登入 Google 帳戶。