Compose笔记(十一)--DataStore(二)
这一节主要了解一下DataStore的源码,datastore-preferences 基于 Flow 和 Coroutines 实现异步数据存储。它的核心原理是将键值对数据存储在一个文件中,通过文件读写操作实现数据的持久化。在读取数据时,会将文件内容解析为键值对;在写入数据时,会将新的键值对更新到文件中。为了保证数据的一致性和并发安全性,使用了锁机制和事务处理。
1 创建 DataStore 实例
当你通过 preferencesDataStore 扩展函数创建 DataStore 实例时,实际上是调用了 DataStoreFactory 的 create 方法。
val Context.dataStore by preferencesDataStore(name = "my_preferences")
preferencesDataStore 扩展函数的实现如下:
fun Context.preferencesDataStore(
name: String,
corruptionHandler: PreferencesDataCorruptionHandler? = null,
migrations: List<DataMigration<Preferences>> = emptyList(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): DataStore<Preferences> = DataStoreFactory.create(
serializer = PreferencesSerializer,
produceFile = { getPreferencesDataStoreFile(name) },
corruptionHandler = corruptionHandler,
migrations = migrations,
scope = scope
)
serializer:指定数据的序列化器,PreferencesSerializer 负责将 Preferences 对象序列化为字节流和从字节流反序列化为 Preferences 对象。
produceFile:返回存储数据的文件。
corruptionHandler:处理数据损坏的回调。
migrations:数据迁移策略列表。
scope:协程作用域,用于执行异步操作。
2. 读取数据
当调用 dataStore.data 获取 Flow<Preferences> 时,会触发数据的读取操作。
val preferencesFlow: Flow<Preferences> = dataStore.data
DataStore 接口的 data 属性实现如下:
override val data: Flow<Preferences> = flow {
while (true) {
val data = readData()
emit(data)
// 监听文件变化
val changeFlow = watchForFileChanges()
changeFlow.collect {
// 文件变化时重新读取数据
val newData = readData()
emit(newData)
}
}
}
readData:从文件中读取数据并解析为 Preferences 对象。
watchForFileChanges:监听文件变化,当文件发生变化时会触发重新读取数据。
3. 写入数据
当调用 dataStore.edit 方法时,会开始一个事务来更新数据。
suspend fun writeData() {
dataStore.edit { preferences ->
preferences[PreferencesKeys.KEY_NAME] = "value"
}
}
edit 方法的实现如下:
override suspend fun edit(transform: suspend (Preferences) -> Preferences): Preferences {
return transaction { currentPreferences ->
val newPreferences = transform(currentPreferences)
writeData(newPreferences)
newPreferences
}
}
transaction:开启一个事务,确保数据的一致性和并发安全性。
transform:用户提供的转换函数,用于更新 Preferences 对象。
writeData:将更新后的 Preferences 对象写入文件。
4. 序列化和反序列化
PreferencesSerializer 负责 Preferences 对象的序列化和反序列化。
object PreferencesSerializer : Serializer<Preferences> {
override val defaultValue: Preferences = Preferences.Empty
override suspend fun readFrom(input: InputStream): Preferences {
return try {
val byteArray = input.readBytes()
Preferences(ByteArrayPreferencesMap(byteArray))
} catch (e: IOException) {
defaultValue
}
}
override suspend fun writeTo(t: Preferences, output: OutputStream) {
val byteArray = (t.map as ByteArrayPreferencesMap).toByteArray()
output.write(byteArray)
}
}
readFrom:从输入流中读取字节数组,并将其解析为 Preferences 对象。
writeTo:将 Preferences 对象转换为字节数组,并写入输出流。
并发控制和数据一致性
datastore-preferences 使用锁机制和事务处理来保证并发控制和数据一致性。在 transaction 方法中,会使用一个锁来确保同一时间只有一个事务可以修改数据。
private suspend fun <T> transaction(transform: suspend (Preferences) -> T): T {
return withContext(scope.coroutineContext) {
lock.withLock {
val currentPreferences = readData()
val result = transform(currentPreferences)
writeData(currentPreferences)
result
}
}
}
简单总结,datastore-preferences 的源码流程主要包括创建 DataStore 实例、读取数据、写入数据和序列化反序列化。通过 Flow 和 Coroutines 实现异步操作,使用锁机制和事务处理保证并发控制和数据一致性。这种设计使得数据存储更加安全、高效和可靠。