DataStore Android Jetpack 的一部分。
Jetpack DataStore 是一項資料儲存解決方案,可讓您使用通訊協定緩衝區儲存鍵/值組合或輸入的物件。DataStore 使用 Kotlin 處理 coroutines 和 Flow,以非同步、一致的方式和交易方式儲存資料。
如果您使用 SharedPreferences 儲存資料,請考慮改用 DataStore。
DataStore API
DataStore 介面提供下列 API:
可用於從 DataStore 讀取資料的 Flow
val data: Flow<T>更新 DataStore 中資料的函式
suspend updateData(transform: suspend (t) -> T)
DataStore 設定
如要使用鍵儲存及存取資料,請使用 Preferences DataStore 實作項目,這項工具不需要預先定義的結構定義,也不會提供型別安全。這個 API 類似於 SharedPreferences,但沒有共用偏好設定的缺點。
DataStore 可讓您保留自訂類別。如要執行這項操作,您必須定義資料的結構定義,並提供 Serializer 將資料轉換為可保存的格式。您可以選擇使用通訊協定緩衝區、JSON 或任何其他序列化策略。
設定
如要在應用程式中使用 Jetpack DataStore,請根據想採用的實作方式,將以下內容新增到 Gradle 檔案:
Preferences DataStore
在 Gradle 檔案的依附元件部分中新增下列幾行:
Groovy
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation "androidx.datastore:datastore-preferences:1.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-preferences-core:1.2.1" }
Kotlin
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation("androidx.datastore:datastore-preferences:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-preferences-core:1.2.1") }
如要新增選用的 RxJava 支援,請新增下列依附元件:
Groovy
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.2.1" }
Kotlin
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.2.1") }
DataStore
在 Gradle 檔案的依附元件部分中新增下列幾行:
Groovy
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation "androidx.datastore:datastore:1.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-core:1.2.1" }
Kotlin
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation("androidx.datastore:datastore:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-core:1.2.1") }
新增下列 RxJava 支援的選用依附元件:
Groovy
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.2.1" }
Kotlin
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.2.1") }
如要序列化內容,請新增通訊協定緩衝區或 JSON 序列化的依附元件。
JSON 序列化
如要使用 JSON 序列化,請在 Gradle 檔案中新增以下內容:
Groovy
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" }
Kotlin
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") }
Protobuf 序列化
如要使用 Protobuf 序列化,請在 Gradle 檔案中新增以下內容:
Groovy
plugins { id("com.google.protobuf") version "0.9.5" } dependencies { implementation "com.google.protobuf:protobuf-kotlin-lite:4.32.1" } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.32.1" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } create("kotlin") } } } }
Kotlin
plugins { id("com.google.protobuf") version "0.9.5" } dependencies { implementation("com.google.protobuf:protobuf-kotlin-lite:4.32.1") } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.32.1" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } create("kotlin") } } } }
正確使用 DataStore
如要正確使用 DataStore,請務必遵守下列規則:
在同一個程序中,針對特定檔案建立的
DataStore執行個體不得超過一個。這麼做可能會導致所有 DataStore 功能無法運作。如果同一個程序中,某個檔案有多個有效的 DataStore,DataStore 會在讀取或更新資料時擲回IllegalStateException。DataStore<T>的泛型型別必須不可變動。如果變更 DataStore 中使用的型別,DataStore 提供的資料一致性就會失效,而且可能會產生難以發現的嚴重錯誤。建議您使用通訊協定緩衝區,確保不變性、明確的 API 和有效率的序列化。請勿對同一個檔案混用
SingleProcessDataStore和MultiProcessDataStore。如要從多個程序存取DataStore,請使用MultiProcessDataStore。
資料定義
Preferences DataStore
定義要用來將資料保留在磁碟上的金鑰。
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
JSON DataStore
如果是 JSON 資料儲存區,請將 @Serialization 註解新增至要保留的資料。
@Serializable data class Settings(val exampleCounter: Int)
定義實作 Serializer<T> 的類別,其中 T 是您先前新增註解的類別類型。請確認您尚未建立序列化器要使用的預設值,如果您還尚未建立任何檔案的話。
object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings(exampleCounter = 0) override suspend fun readFrom(input: InputStream): Settings = 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()) } }
Proto DataStore
Proto DataStore 實作會使用 DataStore 和通訊協定緩衝區,將輸入的物件保留到磁碟。
Proto DataStore 需要 app/src/main/proto/ 目錄中原始檔案的已預先定義的結構定義。這個結構定義定義了您儲存在 Proto DataStore 中的物件類型。如要進一步瞭解如何定義 Proto 結構定義,請參閱 「protobuf 語言指南」。
在 src/main/proto 資料夾中新增名為 settings.proto 的檔案:
syntax = "proto3"; option java_package = "com.example.datastoresampleapp"; option java_multiple_files = true; message Settings { int32 counter = 1; bool foo = 2; }
定義實作 Serializer<T> 的類別,其中 T 是 Proto 檔案中定義的類型。這個序列化器類別會定義 DataStore 讀取及寫入資料類型的方式。請確認您尚未建立序列化器要使用的預設值,如果您還尚未建立任何檔案的話。
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) { return t.writeTo(output) } }
建立 DataStore
您需要指定用於保存資料的檔案名稱。
Preferences DataStore
Preferences DataStore 實作會使用 DataStore 和 Preferences 類別,將鍵/值組合保留在磁碟中。使用 preferencesDataStore 建立的屬性委派來建立 DataStore<Preferences> 的例項。請在 Kotlin 檔案的頂層呼叫一次。在應用程式的其他部分,透過此屬性存取 DataStore。這樣就能更輕鬆地將 DataStore 保持為單例模式。必要的 name 參數是 Preferences DataStore 的名稱。
// At the top level of your kotlin file: val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
JSON DataStore
使用 dataStore 建立的屬性委派來建立 DataStore<T> 的例項,其中 T 是可序列化的資料類別。請先在 Kotlin 檔案的頂層呼叫此方法,然後在應用程式的其餘部分透過此屬性存取該檔案。fileName 參數會告訴 DataStore 要使用哪個檔案儲存資料,而 serializer 參數會指示 DataStore 先前定義的序列化器類別名稱。
val Context.dataStore: DataStore<Settings> by dataStore( fileName = "settings.json", serializer = SettingsSerializer, scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), )
Proto DataStore
使用 dataStore 建立的屬性委派來建立 DataStore<T> 的例項,其中 T 是 proto 檔案中所定義的類型。請先在 Kotlin 檔案的頂層呼叫此方法,然後在應用程式的其餘部分透過此屬性存取該檔案。fileName 參數會告訴 DataStore 要使用哪個檔案儲存資料,而 serializer 參數會指示 DataStore 先前定義的序列化器類別名稱。
val Context.dataStore: DataStore<Settings> by dataStore(fileName = "settings.pb", serializer = SettingsSerializer)
從 DataStore 讀取
您需要指定用於保存資料的檔案名稱。
Preferences DataStore
由於 Preferences DataStore 不使用預先定義的結構定義,因此您必須使用對應的鍵類型函式,為此您需要儲存在 DataStore<Preferences> 例項中的每個值定義鍵。舉例來說,如要為整數值定義鍵,請使用 intPreferencesKey。然後使用 DataStore.data 屬性並用 Flow 公開適當的儲存值。
fun counterFlow(): Flow<Int> = context.dataStore.data.map { preferences -> preferences[EXAMPLE_COUNTER] ?: 0 }
JSON DataStore
使用 DataStore.data 即可從儲存的物件中顯示適當屬性的 Flow。
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings -> settings.exampleCounter }
Proto DataStore
使用 DataStore.data 即可從儲存的物件中顯示適當屬性的 Flow。
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings -> settings.counter }
在可組合函式中,使用 collectAsStateWithLifecycle 消耗 ViewModel 產生的 Flow。這會安全地將 DataStore Flow 轉換為 Compose State,進而觸發重新組合。
@Composable
fun SomeScreen(counterFlow: Flow<Int>) {
val counter by counterFlow.collectAsStateWithLifecycle(initialValue = 0)
Text(text = "Example counter: ${counter}")
}
如要進一步瞭解 collectAsStateWithLifecycle,請參閱「狀態和 Jetpack Compose」。
寫入 DataStore
DataStore 提供 updateData 函式,可交易式更新儲存的物件。updateData 會以資料類型的形式提供資料目前的狀態,並在不可部分完成的讀/寫/改作業中更新資料。updateData 區塊中的所有程式碼都視為單一交易。
Preferences DataStore
suspend fun incrementCounter() { context.dataStore.updateData { it.toMutablePreferences().also { preferences -> preferences[EXAMPLE_COUNTER] = (preferences[EXAMPLE_COUNTER] ?: 0) + 1 } } }
JSON DataStore
suspend fun incrementCounter() { context.dataStore.updateData { settings -> settings.copy(exampleCounter = settings.exampleCounter + 1) } }
Proto DataStore
suspend fun incrementCounter() { context.dataStore.updateData { settings -> settings.toBuilder().setCounter(settings.counter + 1).build() } }
在 Compose 應用程式中使用 DataStore
如要在 Compose 應用程式中使用 DataStore,請遵循 Android 應用程式架構指南,將 DataStore 作業保留在資料層 (例如存放區),並透過 ViewModel 向 UI 公開資料。
請避免在可組合函式中直接讀取或寫入 DataStore。
透過 ViewModel 公開 DataStore。 將存放區 (封裝 DataStore) 傳遞至
ViewModel,並將Flow轉換為StateFlow,讓 UI 可以觀察該存放區,如下列程式碼片段所示:class SettingsViewModel( private val userPreferencesRepository: UserPreferencesRepository ) : ViewModel() { // Expose the DataStore flow as a StateFlow for Compose val userSettings: StateFlow<UserSettings> = userPreferencesRepository.userSettingsFlow .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = UserSettings.getDefaultInstance() ) fun updateCounter(newValue: Int) { viewModelScope.launch { userPreferencesRepository.updateCounter(newValue) } } }從可組合函式觀察及寫入。 在 UI 中安全地觀察
collectAsStateWithLifecycleStateFlow,並呼叫ViewModel函式來處理寫入作業,如下列程式碼片段所示:@Composable fun SettingsScreen( viewModel: SettingsViewModel = viewModel() ) { // Safely collect the state val settings by viewModel.userSettings.collectAsStateWithLifecycle() Column(modifier = Modifier.padding(16.dp)) { Text(text = "Current counter: ${settings.counter}") Spacer(modifier = Modifier.height(8.dp)) Button(onClick = { viewModel.updateCounter(settings.counter + 1) }) { Text("Increment Counter") } } }
在多程序程式碼中使用 DataStore
您可以設定 DataStore,在不同程序中存取相同資料,並使用與單一程序內相同的資料一致性屬性。特別是,DataStore 提供下列屬性:
- 讀取作業只會傳回已保存到磁碟的資料。
- 寫入後讀取一致性。
- 寫入作業會序列化。
- 讀取作業絕不會遭到寫入作業封鎖。
假設有一個範例應用程式,其中包含一項服務和一項活動,而這項服務是在個別程序中執行,並定期更新 DataStore。
這個範例使用 JSON Datastore,但您也可以使用 Preferences 或 Proto DataStore。
@Serializable data class Time(val lastUpdateMillis: Long)
序列化器會告知 DataStore 如何讀取及寫入您的資料類型。請確認您尚未建立序列化器要使用的預設值,如果您還尚未建立任何檔案的話。以下是使用 kotlinx.serialization 的實作範例:
object TimeSerializer : Serializer<Time> { override val defaultValue: Time = Time(lastUpdateMillis = 0L) override suspend fun readFrom(input: InputStream): Time = try { Json.decodeFromString(Time.serializer(), input.readBytes().decodeToString()) } catch (serialization: SerializationException) { throw CorruptionException("Unable to read Time", serialization) } override suspend fun writeTo(t: Time, output: OutputStream) { output.write(Json.encodeToString(Time.serializer(), t).encodeToByteArray()) } }
如要在不同程序中使用 DataStore,您需要使用應用程式和服務程式碼的 MultiProcessDataStoreFactory 建構 DataStore 物件:
val dataStore = MultiProcessDataStoreFactory.create( serializer = TimeSerializer, produceFile = { context.dataStoreFile("time.pb") }, corruptionHandler = null, )
請將以下內容新增到 AndroidManifiest.xml:
<service android:name="com.example.datastore.snippets.TimestampUpdateService" android:exported="false" android:process=":service" />
服務會定期呼叫 updateLastUpdateTime,這會使用 updateData 寫入資料儲存庫。
suspend fun updateLastUpdateTime() { dataStore.updateData { time -> time.copy(lastUpdateMillis = System.currentTimeMillis()) } }
應用程式會使用資料流程讀取服務寫入的值:
fun timeFlow(): Flow<Long> = dataStore.data.map { time -> time.lastUpdateMillis }
現在,我們可以將所有這些函式放在名為 MultiProcessDataStore 的類別中,並在應用程式中使用。
服務代碼如下:
class TimestampUpdateService : Service() { val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) val multiProcessDataStore by lazy { MultiProcessDataStore(applicationContext) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { serviceScope.launch { while (true) { multiProcessDataStore.updateLastUpdateTime() delay(1000) } } return START_NOT_STICKY } override fun onDestroy() { super.onDestroy() serviceScope.cancel() } }
應用程式程式碼:
val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val multiProcessDataStore = remember(context) { MultiProcessDataStore(context) } // Display time written by other process. val lastUpdateTime by multiProcessDataStore .timeFlow() .collectAsState(initial = 0, coroutineScope.coroutineContext) Text(text = "Last updated: $lastUpdateTime", fontSize = 25.sp) DisposableEffect(context) { val serviceIntent = Intent(context, TimestampUpdateService::class.java) context.startService(serviceIntent) onDispose { context.stopService(serviceIntent) } }
您可以使用 Hilt 依附元件插入,確保每個程序都有專屬的 DataStore 執行個體:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
處理檔案毀損
在極少數情況下,DataStore 的磁碟上永久檔案可能會毀損。根據預設,DataStore 不會自動從損毀狀態復原,嘗試從中讀取資料會導致系統擲回 CorruptionException。
DataStore 提供損毀處理常式 API,可協助您在這種情況下順利復原,並避免擲回例外狀況。設定完成後,損毀處理常式會將損毀的檔案替換為包含預先定義預設值的新檔案。
如要設定這個處理常式,請在 by dataStore 中建立 DataStore 例項時,或在 DataStoreFactory 工廠方法中提供 corruptionHandler:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.filesDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
備份及還原裝置
Preferences DataStore 檔案 (*.preferences_pb) 和 Proto DataStore 檔案會儲存在應用程式的 files/datastore/ 目錄中。根據預設,這些檔案會納入 Android 自動備份和裝置間 (D2D) 轉移作業。
設定 DataStore 的備份規則
如果您的 DataStore 包含非機密偏好設定 (例如使用者主題或功能標記) 和機密資料,請將兩者分別儲存在不同的 DataStore 檔案中,並設定 res/xml/data_extraction_rules.xml:
<data-extraction-rules>
<cloud-backup>
<!-- Include general settings -->
<include domain="file" path="datastore/user_settings.preferences_pb"/>
<!-- Exclude sensitive local state -->
<exclude domain="file" path="datastore/secure_state.preferences_pb"/>
</cloud-backup>
<device-to-device>
<!-- Transfer settings during device-to-device transfer -->
<include domain="file" path="datastore/"/>
</device-to-device>
</data-extraction-rules>
提供意見
歡迎透過下列資源與我們分享意見和想法:
- Issue Tracker:
- 報告問題,幫助我們修正錯誤。
其他資源
如要進一步瞭解 Jetpack DataStore,請參閱下列其他資源:
範例
網誌
程式碼研究室
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 載入並顯示分頁資料
- LiveData 總覽
- 版面配置與繫結的運算式