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.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(...)
提供反馈
通过以下资源与我们分享你的反馈和想法。
- 问题跟踪器
- 报告问题,以便我们能够修复错误。
其他资源
若要了解有关 Jetpack DataStore 的更多信息,请参阅以下其他资源。
示例
博客
Codelabs
推荐给您
- 注意:当 JavaScript 关闭时,链接文本会显示出来。
- 加载和显示分页数据
- LiveData 概述
- 布局和绑定表达式