DataStore 属于 Android Jetpack。
Jetpack DataStore 是一种数据存储解决方案,允许您使用Protocol Buffers存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 来异步、一致且事务性地存储数据。
如果您目前正在使用SharedPreferences
存储数据,请考虑迁移到 DataStore。
Preferences DataStore 和 Proto DataStore
DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。
- Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的模式,也不提供类型安全。
- Proto DataStore 将数据存储为自定义数据类型的实例。此实现要求您使用Protocol Buffers定义模式,但它提供类型安全。
正确使用DataStore
为了正确使用 DataStore,请始终牢记以下规则
切勿为同一进程中的给定文件创建多个
DataStore
实例。这样做会破坏所有 DataStore 功能。如果同一进程中给定文件的多个 DataStore 处于活动状态,则 DataStore 在读取或更新数据时将抛出IllegalStateException
。DataStore 的泛型类型必须是不可变的。更改 DataStore 中使用的类型会使 DataStore 提供的任何保证无效,并可能创建难以发现的严重错误。强烈建议您使用 Protocol Buffers,它提供不可变性保证、简单的 API 和高效的序列化。
切勿将
SingleProcessDataStore
和MultiProcessDataStore
混合用于同一文件。如果您打算从多个进程访问DataStore
,请始终使用MultiProcessDataStore
。
设置
要在您的应用中使用 Jetpack DataStore,请根据您要使用的实现将以下内容添加到您的 Gradle 文件中
Preferences DataStore
Groovy
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.1" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.1" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.1" }
Kotlin
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.1") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.1") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.1") }
Proto DataStore
Groovy
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.1" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.1" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.1" }
Kotlin
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.1") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.1") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.1") }
使用 Preferences DataStore 存储键值对
Preferences DataStore 实现使用DataStore
和Preferences
类将简单的键值对持久化到磁盘。
创建 Preferences DataStore
使用preferencesDataStore
创建的属性委托来创建DataStore<Preferences>
的实例。在 Kotlin 文件的顶层调用它一次,并在应用程序的其余部分通过此属性访问它。这使得更容易将您的DataStore
保持为单例。或者,如果您使用的是 RxJava,请使用RxPreferenceDataStoreBuilder
。必需的name
参数是 Preferences DataStore 的名称。
Kotlin
// At the top level of your kotlin file: val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Java
RxDataStore<Preferences> dataStore = new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();
从 Preferences DataStore 读取数据
由于 Preferences DataStore 不使用预定义的模式,您必须使用相应的键类型函数为需要存储在DataStore<Preferences>
实例中的每个值定义一个键。例如,要为 int 值定义一个键,请使用intPreferencesKey()
。然后,使用DataStore.data
属性使用Flow
公开相应存储的值。
Kotlin
val EXAMPLE_COUNTER = intPreferencesKey("example_counter") val exampleCounterFlow: Flow<Int> = context.dataStore.data .map { preferences -> // No type safety. preferences[EXAMPLE_COUNTER] ?: 0 }
Java
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
参数接受一个代码块,您可以在其中根据需要更新值。转换块中的所有代码都被视为单个事务。
Kotlin
suspend fun incrementCounter() { context.dataStore.edit { settings -> val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0 settings[EXAMPLE_COUNTER] = currentCounterValue + 1 } }
Java
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 文件中的预定义模式。此模式定义了在 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 中定义的序列化器类的名称。
Kotlin
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 )
Java
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
。
Kotlin
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data .map { settings -> // The exampleCounter property is generated from the proto schema. settings.exampleCounter }
Java
Flowable<Integer> exampleCounterFlow = dataStore.data().map(settings -> settings.getExampleCounter());
写入 Proto DataStore
Proto DataStore 提供了一个updateData()
函数,该函数以事务方式更新存储的对象。updateData()
为您提供数据的当前状态(作为数据类型的实例),并以原子读-写-修改操作以事务方式更新数据。
Kotlin
suspend fun incrementCounter() { context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build() } }
Java
Single<Settings> updateResult = dataStore.updateDataAsync(currentSettings -> Single.just( currentSettings.toBuilder() .setExampleCounter(currentSettings.getExampleCounter() + 1) .build()));
在同步代码中使用 DataStore
DataStore 的主要优点之一是异步 API,但更改周围代码以使其异步并不总是可行的。如果您正在处理使用同步磁盘 I/O 的现有代码库,或者您有一个不提供异步 API 的依赖项,则可能出现这种情况。
Kotlin 协程提供runBlocking()
协程构建器来帮助弥合同步和异步代码之间的差距。您可以使用runBlocking()
同步地从 DataStore 读取数据。RxJava 在Flowable
上提供阻塞方法。以下代码会阻塞调用线程,直到 DataStore 返回数据。
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
在 UI 线程上执行同步 I/O 操作可能会导致 ANR 或 UI 卡顿。您可以通过异步预加载 DataStore 中的数据来减轻这些问题。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { context.dataStore.data.first() // You should also handle IOExceptions here. } }
Java
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) }
)
提供反馈
通过以下资源与我们分享您的反馈和想法
- 问题跟踪器
- 报告问题,以便我们修复错误。
其他资源
要了解有关 Jetpack DataStore 的更多信息,请参阅以下其他资源
示例
博客
代码实验室
为您推荐
- 注意:关闭 JavaScript 时显示链接文本
- 加载和显示分页数据
- LiveData 概述
- 布局和绑定表达式