1.先看一下dataStore的使用,本身edit是一个suspend函数,所以必须在协程中使用。
suspend fun test(context: Context) {
val TEST_KEY = stringPreferencesKey("test")
context.myDataStore.edit {
val name = it[TEST_KEY]
it[TEST_KEY] = name + "123"
}
}
2.走进edit()函数看一眼
public suspend fun DataStore<Preferences>.edit(
transform: suspend (MutablePreferences) -> Unit
): Preferences {
return this.updateData {
// It's safe to return MutablePreferences since we freeze it in
// PreferencesDataStore.updateData()
it.toMutablePreferences().apply { transform(this) }
}
}
3.走进update函数看一眼
override suspend fun updateData(transform: suspend (t: T) -> T): T {
/**
* The states here are the same as the states for reads. Additionally we send an ack that
* the actor *must* respond to (even if it is cancelled).
*/
val ack = CompletableDeferred<T>()
val currentDownStreamFlowState = downstreamFlow.value
val updateMsg =
Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
actor.offer(updateMsg)
return ack.await()
}
这里CompletableDeferred,然后调用了await(),说明此时协程要等待这个ack complete,只要找到complete()函数调用就可以了。 downstreamFlow用于标记state,初始是UnInitialized,本身是在Seal class里面。 里面先把我们的transfrom封装到message里面,然后关键是actor.offer()的调用。
4.走进offer()看一眼:
fun offer(msg: T) {
check(
messageQueue.trySend(msg)
.onClosed { throw it ?: ClosedSendChannelException("Channel was closed normally") }
.isSuccess
)
// If the number of remaining messages was 0, there is no active consumer, since it quits
// consuming once remaining messages hits 0. We must kick off a new consumer.
if (remainingMessages.getAndIncrement() == 0) {
scope.launch {
// We shouldn't have started a new consumer unless there are remaining messages...
check(remainingMessages.get() > 0)
do {
// We don't want to try to consume a new message unless we are still active.
// If ensureActive throws, the scope is no longer active, so it doesn't
// matter that we have remaining messages.
scope.ensureActive()
consumeMessage(messageQueue.receive())
} while (remainingMessages.decrementAndGet() != 0)
}
}
}
messageQueue本身是一个channel,用于多个协程之间进行通信,核心api是send(),receive()。 这里先把消息send出去,然后收到之后,调用consumeMessage()
-
consumeMessage本身是构建actor的一个函数参数:
{ msg -> when (msg) { is Message.Read -> { handleRead(msg) } is Message.Update -> { handleUpdate(msg) } }
6.这里去update看一眼:
private suspend fun handleUpdate(update: Message.Update<T>) {
// All branches of this *must* complete ack either successfully or exceptionally.
// We must *not* throw an exception, just propagate it to the ack.
update.ack.completeWith(
runCatching {
when (val currentState = downstreamFlow.value) {
is Data -> {
// We are already initialized, we just need to perform the update
transformAndWrite(update.transform, update.callerContext)
}
is ReadException, is UnInitialized -> {
if (currentState === update.lastState) {
// we need to try to read again
readAndInitOrPropagateAndThrowFailure()
// We've successfully read, now we need to perform the update
transformAndWrite(update.transform, update.callerContext)
} else {
// Someone else beat us to read but also failed. We just need to
// signal the writer that is waiting on ack.
// This cast is safe because we can't be in the UnInitialized
// state if the state has changed.
throw (currentState as ReadException).readException
}
}
is Final -> throw currentState.finalException // won't happen
}
}
)
}
update.ack.completeWith(),看到了熟悉的complete,热泪盈眶的感觉,所以处理消息就在这了。
7.终于快要调用transform了,胜利近在眼前。
private suspend fun transformAndWrite(
transform: suspend (t: T) -> T,
callerContext: CoroutineContext
): T {
// value is not null or an exception because we must have the value set by now so this cast
// is safe.
val curDataAndHash = downstreamFlow.value as Data<T>
curDataAndHash.checkHashCode()
val curData = curDataAndHash.value
val newData = withContext(callerContext) { transform(curData) }
// Check that curData has not changed...
curDataAndHash.checkHashCode()
return if (curData == newData) {
curData
} else {
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
newData
}
}
核心调用: val newData = withContext(callerContext) { transform(curData) } 如果修改完数据,会比较hash值,如果只是读数据,hash值一样,直接return了,如果是write数据:
8.write数据 internal suspend fun writeData(newData: T) { file.createParentDirectories()
val scratchFile = File(file.absolutePath + SCRATCH_SUFFIX)
try {
FileOutputStream(scratchFile).use { stream ->
serializer.writeTo(newData, UncloseableOutputStream(stream))
stream.fd.sync()
// TODO(b/151635324): fsync the directory, otherwise a badly timed crash could
// result in reverting to a previous state.
}
if (!scratchFile.renameTo(file)) {
throw IOException(
"Unable to rename $scratchFile." +
"This likely means that there are multiple instances of DataStore " +
"for this file. Ensure that you are only creating a single instance of " +
"datastore for this file."
)
}
} catch (ex: IOException) {
if (scratchFile.exists()) {
scratchFile.delete() // Swallow failure to delete
}
throw ex
}
}
开启了一个FileOutputStream,use()函数十分常用,尤其是流的读写的时候。 可以看到其读写,本质是先定义了.tmp的备份文件,然后等写成功之后,再进行rename操作。 这里还进行了stream.fd.sync(),是一个native函数。