解析 mutableStateOf 源码 -- 扒皮 Compose 的状态订阅&自动刷新机制

在这里插入图片描述

其实原理性分析的文章,真的很难讲的通俗易懂,讲的简单浅显没必要写,讲的繁琐难懂往往大家也不乐意看,所以怎么找个好的角度慢慢钻进去尤为重要,比如:Begin simple code,如果确实能帮助到大家完全理解了文章所讲述到的源码理论,那就点个赞吧!

引入话题


正如我上面说的,直接讲原理太枯燥(你也会很懵),我喜欢从简单代码入手,带你一点点进入,现在开始。先看一个最简单的代码示例:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 这种定义变量的方式随处可见了
        val name = mutableStateOf("Compose")

        setContent {
            ComposeBlogTheme {
                Text(name.value)
            }
        }
    }
}

我先来简单解读一下这段代码的原理:

  1. 当我们定义一个变量,用 mutableStateOf 包起来后,它就变成了一个 MutableState 类型的对象。
  2. 同时,我们取值的话就必须要写 name.value,这样能才能取到 “Compose”,因为name 不再是一个 String,而是 MutableState 对象,也可以叫“状态” 。
  3. 此时,name 是一个被订阅的状态,name.value 就是一个被订阅的值,如果它发生变化,Text() 函数就会重新执行一遍,更新到最新的值。

现在我们修改下代码,3s 后改变 name.value 的值:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val name = mutableStateOf("Compose")

        setContent {
            ComposeBlogTheme {
                Text(name.value)
            }

            LaunchedEffect(true) {
                delay(3000) // 3s 延迟
                name.value = "Kotlin"  // 修改 name.value
            }
        }
    }
}

运行下:

在这里插入图片描述

现在关于 mutableStateOf 的用法你已经掌握了,但同时就会产生了一个疑问,name 被自动订阅了,它的值改变了就会让界面重新刷新,这背后的“状态订阅&刷新机制”的原理是什么?

如果你想深入了解,那么接着往下看。


状态订阅&自动刷新


基于 androidx.compose.runtime:runtime:1.4.0 版本

硬核部分走起,查看 mutableStateOf() 源码:

fun <T> mutableStateOf(
    value: T,
    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
  1. mutableStateOf() 返回的是一个 MutableState 对象,这个我们前面说过。
  2. mutableStateOf() 又调用了另一个函数:createSnapshotMutableState()

进入 createSnapshotMutableState()

internal actual fun <T> createSnapshotMutableState(
    value: T,
    policy: SnapshotMutationPolicy<T>
): SnapshotMutableState<T> = ParcelableSnapshotMutableState(value, policy)

又调用了 ParcelableSnapshotMutableState() 函数:

internal class ParcelableSnapshotMutableState<T>(
    value: T,
    policy: SnapshotMutationPolicy<T>
) : SnapshotMutableStateImpl<T>(value, policy), Parcelable {
    // 这里内部的代码全部都是对 Parcelable 接口的实现,我们不用关心,不是核心内容
	... ...
}

关键在 SnapshotMutableStateImpl,它里面才是最核心的逻辑:

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
        get() = next.readable(this).value
        set(value) = next.withCurrent {
            if (!policy.equivalent(it.value, value)) {
                next.overwritable(this, it) { this.value = value }
            }
        }

    private var next: StateStateRecord<T> = StateStateRecord(value)

    override val firstStateRecord: StateRecord
        get() = next

    override fun prependStateRecord(value: StateRecord) {
        @Suppress("UNCHECKED_CAST")
        next = value as StateStateRecord<T>
    }

    @Suppress("UNCHECKED_CAST")
    override fun mergeRecords(
        previous: StateRecord,
        current: StateRecord,
        applied: StateRecord
    ): StateRecord? {
        val previousRecord = previous as StateStateRecord<T>
        val currentRecord = current as StateStateRecord<T>
        val appliedRecord = applied as StateStateRecord<T>
        return if (policy.equivalent(currentRecord.value, appliedRecord.value))
            current
        else {
            val merged = policy.merge(
                previousRecord.value,
                currentRecord.value,
                appliedRecord.value
            )
            if (merged != null) {
                appliedRecord.create().also {
                    (it as StateStateRecord<T>).value = merged
                }
            } else {
                null
            }
        }
    }

    override fun toString(): String = next.withCurrent {
        "MutableState(value=${it.value})@${hashCode()}"
    }

    private class StateStateRecord<T>(myValue: T) : StateRecord() {
        override fun assign(value: StateRecord) {
            @Suppress("UNCHECKED_CAST")
            this.value = (value as StateStateRecord<T>).value
        }

        override fun create(): StateRecord = StateStateRecord(value)

        var value: T = myValue
    }

    /**
     * The componentN() operators allow state objects to be used with the property destructuring
     * syntax
     *
     * ```
     * var (foo, setFoo) = remember { mutableStateOf(0) }
     * setFoo(123) // set
     * foo == 123 // get
     * ```
     */
    override operator fun component1(): T = value

    override operator fun component2(): (T) -> Unit = { value = it }

    /**
     * A function used by the debugger to display the value of the current value of the mutable
     * state object without triggering read observers.
     */
    @Suppress("unused")
    val debuggerDisplayValue: T
        @JvmName("getDebuggerDisplayValue")
        get() = next.withCurrent { it }.value
}

好长啊~算了,不看了🙈…

接下来我带你一步步探索其中的奥秘!

先看开头部分:

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
        get() = next.readable(this).value
        set(value) = next.withCurrent {
            if (!policy.equivalent(it.value, value)) {
                next.overwritable(this, it) { this.value = value }
            }
        }

    private var next: StateStateRecord<T> = StateStateRecord(value)

    override val firstStateRecord: StateRecord
        get() = next
    ... ...
}
  1. 一个 value 属性印入眼帘,这就是文章开头代码里面用到的 name.value
  2. value 有 get()set() 函数,并且都有具体的实现。

📓 next 是个啥?


无论是 get() 还是 set() 都有一个 next

get() = next.readable(this).value
set(value) = next.withCurrent {...}

它是个啥?我们得先搞明白这个。

private var next: StateStateRecord<T> = StateStateRecord(value)

它的类型是 StateStateRecord,那 StateStateRecord 有是个啥?

private class StateStateRecord<T>(myValue: T) : StateRecord() {
    override fun assign(value: StateRecord) {
        @Suppress("UNCHECKED_CAST")
        this.value = (value as StateStateRecord<T>).value
    }

    override fun create(): StateRecord = StateStateRecord(value)

    var value: T = myValue
}

StateStateRecord 继承了 StateRecord,那 StateRecord 又是个啥呢?

abstract class StateRecord {
    internal var snapshotId: Int = currentSnapshot().id    // 记录快照 id
    internal var next: StateRecord? = null    // 下一个状态记录的引用,状态记录存储在一个链表中
    abstract fun assign(value: StateRecord)   // 复制 StateRecord
    abstract fun create(): StateRecord        // 创建一个新的记录相同的 StateRecord
}

你只要知道 StateRecord 是一个链表的数据结构即可,而 StateStateRecord 实现了它,并且将 value 进行了封装。

下面我们要讲别的了,先记住:StateRecord 是一个链表的数据结构!

接着说,如果你仔细看代码的话,会发现 SnapshotMutableStateImpl 其实继承了两个接口:

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    ... ...
}

一个 StateObjet、一个 SnapshotMutableState

我们先来看下 SnapshotMutableState

interface SnapshotMutableState<T> : MutableState<T> {
    /**
     * A policy to control how changes are handled in a mutable snapshot.
     */
    val policy: SnapshotMutationPolicy<T>
}

SnapshotMutableState 继承了 MutableState,正好对应着我们文章开头说的,mutableStateOf() 返回的就是一个 MutableState,我们说是因为它实现了订阅从而可以刷新,但!!!真正的原因并不是因为它!

真正实现状态订阅机制的是另外一个接口:StateObjet

我们现在来看下 StateObject 这个大佬干了什么事:

interface StateObject {
    /**
     * The first state record in a linked list of state records.
     */
    val firstStateRecord: StateRecord

    fun prependStateRecord(value: StateRecord)

    fun mergeRecords(
        previous: StateRecord,
        current: StateRecord,
        applied: StateRecord
    ): StateRecord? = null
}

代码非常简单,但里面有一个核心

val firstStateRecord: StateRecord

它是干嘛用的?我们去瞅瞅哪里用了它:

private var next: StateStateRecord<T> = StateStateRecord(value)
    
override val firstStateRecord: StateRecord
    get() = next

哦,原来 firstStateRecord 是用来记录 StateRecord 这个链表的 头节点 用的。


📓 get()


上面应该算是把 next 是什么讲清楚了吧?-- 它就是 StateRecord,更准确的说就是 StateRecord 链表的头节点

下面我们开始扒扒 get() 具体的逻辑了。

get() = next.readable(this).value

进入 readable() :

fun <T : StateRecord> T.readable(state: StateObject): T {
    val snapshot = Snapshot.current
    // 第一件事
    snapshot.readObserver?.invoke(state)
    // 第二件事
    return readable(this, snapshot.id, snapshot.invalid) ?: sync {
        val syncSnapshot = Snapshot.current
        @Suppress("UNCHECKED_CAST")
        readable(state.firstStateRecord as T, syncSnapshot.id, syncSnapshot.invalid) ?: readError()
    }
}

干了两件事,第一件事:

snapshot.readObserver?.invoke(state)    // 第一件事

readObserver 是一个读操作的观察者,这个操作是记录 StateObject 中的值被哪里调用了,比如开头的代码示例:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val name = mutableStateOf("Compose")

        setContent {
            ComposeBlogTheme {
                // 这里调用了 name.value,就会执行到:
                // get() --> readServer() --> 记录这里调用了 value 值
                Text(name.value)
            }

            LaunchedEffect(true) {
                delay(3000)
                name.value = "Kotlin"
            }
        }
    }
}

所以,我们可以把这个操作理解为它是一个订阅操作:订阅状态,记录 name.value 在哪里调用了。

第二件事:

return readable(this, snapshot.id, snapshot.invalid) ?: sync {
    val syncSnapshot = Snapshot.current
    @Suppress("UNCHECKED_CAST")
    readable(state.firstStateRecord as T, syncSnapshot.id, syncSnapshot.invalid) ?: readError()
}

调用了三参数的 readable():

private fun <T : StateRecord> readable(r: T, id: Int, invalid: SnapshotIdSet): T? {
    // The readable record is the valid record with the highest snapshotId
    var current: StateRecord? = r
    var candidate: StateRecord? = null
    while (current != null) {
        if (valid(current, id, invalid)) {
            candidate = if (candidate == null) current
            else if (candidate.snapshotId < current.snapshotId) current else candidate
        }
        current = current.next
    }
    if (candidate != null) {
        @Suppress("UNCHECKED_CAST")
        return candidate as T
    }
    return null
}

前边我们说错:StateRecord 是一个链表,三参数里面的工作就是通过表头对象遍历列表来获取 最新的有效的 StateRecord。

最终返回一个 最新的有效的 StateRecord。(什么是最新的、有效的,这个可以不用关心,这跟要结合 Snapshot 快照系统说,你直接过滤即可,不影响我们原理的理解)

所以,整个 readable() 方法就干了两件事:

  1. 返回最新的、有效的 StateRecord
  2. 记录在哪里调用了 value
get() = next.readable(this).value

最后还剩一个 value,这就很简单了,前面我们说过 StateStateRecord (StateRecord 的实现类) 会对 value 进行封装,而 StateRecord 它们只是一个链表,我们要获取到值,就要再调用 value 获取内部被包着的值。

到这里 get() 就讲完了!


📓 set()


现在来看 set() 的具体代码:(细节点注意:这边我把代码截图了,而不是纯代码段,你留意一下,下面会回归到这里)

在这里插入图片描述

next 是啥不用说了吧,那 withCurrent 是什么?

inline fun <T : StateRecord, R> T.withCurrent(block: (r: T) -> R): R =
    block(current(this, Snapshot.current))

它只有一个参数 block,而 block 是一个函数类型的参数,它的工作很直接,直接调用这个函数类型的参数,也就是 withCurrent 后面跟着的 Lambda 表达式。

在这里插入图片描述

block 里面传入了一个 current 函数,它做了什么:

internal fun <T : StateRecord> current(r: T, snapshot: Snapshot) =
    readable(r, snapshot.id, snapshot.invalid) ?: readError()

发现了什么?它调用了一个三参数的 readable(),你还记得三参数的 readable() 是做什么的吗?

⇒ 获取 最新的有效的 StateRecord。

这个时候你在看下我开头为啥对代码做了截图:

在这里插入图片描述

懂啥意思没?withCurrent 传入的 current 函数的返回值就对应着代码提示器提示的 it 对象(一个 StateStateRecord)。

接着看代码:

set(value) = next.withCurrent {
    // 判断新旧值
    if (!policy.equivalent(it.value, value)) {
        next.overwritable(this, it) { this.value = value }
    }
}

if 判断里面会判断取到的 StateRecord 的值和新设置的值是否相同,没变直接结束,变了就进入下一步 overwritable()。

我们看看它做了什么:

internal inline fun <T : StateRecord, R> T.overwritable(
    state: StateObject,
    candidate: T,
    block: T.() -> R
): R {
    var snapshot: Snapshot = snapshotInitializer
    return sync {
        // 第一件事
        snapshot = Snapshot.current
        // 第二件事
        this.overwritableRecord(state, snapshot, candidate).block()
    }.also {
        notifyWrite(snapshot, state)
    }
}

也干了两件事:

  1. 获取快照;2. 调用了overwritableRecord() 函数;

Snapshot 快照的知识我们可以先忽略,来看 overwritableRecord() 做了什么:

internal fun <T : StateRecord> T.overwritableRecord(
    state: StateObject,
    snapshot: Snapshot,
    candidate: T
): T {
    if (snapshot.readOnly) {
        snapshot.recordModified(state)
    }
    val id = snapshot.id

    if (candidate.snapshotId == id) return candidate

    val newData = newOverwritableRecord(state)
    newData.snapshotId = id

    snapshot.recordModified(state)

    return newData
}

这段代码又涉及到了另外一个知识点:Snapshot(快照)。

我们将代码分为两端来看:

在这里插入图片描述

核心代码块 1:如果传进来的 StateRecord 的快照 id 正好对应当前 snapshot 的 id,那么直接返回。

核心代码块 2:否则会创建一个新的或者返回一个弃用的 StateRecord,然后将快照 id 赋予新的 StateRecord。

说白了最终就是要取到一个对应当前 snapshot 的 StateRecord!

🤔 此时就存在一个很大的疑问了?什么是 Snapshot(快照)?Snapshot 怎么和 StateRecord 绑在一起了?

关于 Compose 的 Snapshot 机制,可以看看这篇文章 揭秘 Jetpack Compose
快照系统
,讲的很好。

但由于 Snapshot 真的不是一两句就能说清楚的,但我仍然告诉你它不影响你对这篇文章原理的理解。

继续回到代码:

internal inline fun <T : StateRecord, R> T.overwritable(
    state: StateObject,
    candidate: T,
    block: T.() -> R
): R {
    var snapshot: Snapshot = snapshotInitializer
    return sync {
        snapshot = Snapshot.current
        // 取到一个对应当前 snapshot 的 StateRecord
        this.overwritableRecord(state, snapshot, candidate).block()
    }.also {
        notifyWrite(snapshot, state)
    }
}

取到对应当前 snapshot 的 StateRecord 后,紧接着调用了 block(),它是传进来的参数,是外面传进来的,也就是 {this.value = value}

set(value) = next.withCurrent {
    // 判断新旧值
    if (!policy.equivalent(it.value, value)) {
        next.overwritable(this, it) { this.value = value }
    }
}

又拐回来了,把传入的新值赋值给拿到的 StateRecord 内部的 value,这不就是写新值的操作么?

到这里是不是 set() 流程是不是我们就讲完了? No~,我们再看一下 overwritable() 方法内部:

internal inline fun <T : StateRecord, R> T.overwritable(
    state: StateObject,
    candidate: T,
    block: T.() -> R
): R {
    var snapshot: Snapshot = snapshotInitializer
    return sync {
        snapshot = Snapshot.current
         // 这里讲完了
        this.overwritableRecord(state, snapshot, candidate).block()
    }.also {
        notifyWrite(snapshot, state)  // 但这里还有个 notifyWrite
    }
}

还有一行我们遗漏了,notifyWrite() 又做了什么?

internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
    snapshot.writeObserver?.invoke(state)
}

有种似曾相识的感觉,在前面分析 get() 函数的时候,我们看到过单参数的 readable() 里面有一行:

snapshot.readObserver?.invoke(state)

那这里的作用呢?

寻找变量在哪里被读了,然后将这部分内容的组合标记为失效,等到下一帧的时候会重组刷新。

所以,整个 set() 方法就干了两件事:

  1. 将传入的值赋值给 StateRecord 内部的 value
  2. 写入通知刷新

到这里我们就基本上能够很清晰的明白了状态订阅&自动刷新机制的原理了:

  1. 当 get() 被调用的时候,不仅返回值,还会记录读值的地方,也就是哪里调用了(相当于订阅)。
  2. 当 set() 被调用的时候,不仅修改值,还会查找读值的地方,然后进行刷新操作(相当于通知)。

📓 by


在实际开发中,如果每次获取值都要加上 value 会显得很冗余,所以 Compose 给我们提供了一种更方便的写法:by。它是 Kotlin 的一个关键字,表示左边的变量用右边的对象作为代理(委托)。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val name by mutableStateOf("Compose")

        setContent {
            ComposeBlogTheme {
                Text(name)    // 可以直接 name,而不需要 name.value
            }
        }
    }
}

如果你这么写,IDLE 会提示报错,因为 name 委托给了 mutableStateOf,如果我们要获取 name 的值,那委托对象需要调用 getValue() 和 setValue() 两个函数,这两个函数需要自己实现。

在这里插入图片描述

但实际上并不需要我们实现,你通过提示也可以看出来,我们可以直接导入,因为 Compose 内部已经帮我们实现好了这个方法。

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

自此,关于 Compose 的状态订阅&自动刷新机制的原理算是讲明白了吧…

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值