DataStore源码解析

479 阅读3分钟

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()

  1. 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函数。