DataStore 是 Android Jetpack 的一部分。
Jetpack DataStore 是一种数据存储解决方案,允许您使用 协议缓冲区 存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 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
Groovy
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.7" }
Kotlin
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.7") }
Proto DataStore
Groovy
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.7" }
Kotlin
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.7") }
使用 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.proto";
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();
在界面线程上执行同步 I/O 操作可能导致 ANR 或界面卡顿。您可以通过异步预加载 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 保证
- 读取只返回已持久化到磁盘的数据。
- 写后读一致性。
- 写入操作是序列化的。
- 读取操作永远不会被写入操作阻塞。
考虑一个包含服务和 Activity 的示例应用
该服务在单独的进程中运行,并定期更新 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) } } }
同时应用会收集这些更改并更新其界面
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): 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()
)
}
}
您可以使用 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 的更多信息,请参阅以下其他资源
示例
博客
Codelab
为您推荐
- 注意:当 JavaScript 关闭时显示链接文本
- 加载并显示分页数据
- LiveData 概览
- 布局和绑定表达式