Compose:State 状态订阅更新原理

Compose 怎么更新界面?

Compose 既然是声明式 UI,那肯定就不能像传统 UI 那样命令式的方式更新界面,声明式的界面更新就是,你把值改了界面就会自动更新,你的参数会被 Compose 自动监听

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 注意这里是用 =
        // mutableStateOf 返回的 MutableState 对象
        val name = mutableStateOf("name")
        setContent {
        	// 赋值使用 name.value
			Text(name.value)
		}
		lifecycleScope.launch {
			delay(3000)
			// 修改数值,就能通知到 Composable 刷新
			name.value = "vincent"
		}
    }
}    

上面是一个文字的 Composable,开启了一个协程 3s 后修改 name 的值,Compose 监听到 name 改变了,就会同步将 Text() 也更新。

需要注意的是,在 Compose 中要实现参数被监听,参数要使用 Compose 提供的 MutableState 类型才能被监听,在写法上使用 mutableStateOf 函数创建:

val name = mutableStateOf("name")

SnapshotState.kt

@Stable
interface MutableState<T> : State<T> {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -> Unit
}

Compose 订阅更新原理

为什么通过 mutableStateOf 函数返回的对象就能监听实现值改变 Composable 的内容也跟着改变?

根据我们以往的开发经验,我们可以先猜测下 Compose 会怎么实现这个逻辑:

  • 数据更新就会通知 Composable 刷新改变,也就是可能会涉及到观察者模式,会需要数据的监听和订阅

  • 是什么时候完成的订阅和监听?又是怎样完成的通知刷新?

我们带着上面两个问题,详细讲解下 mutableStateOf 的原理。

在上面有提到,获取 MutableState 是通过 mutableStateOf 函数创建:

SnapshotState.kt

fun <T> mutableStateOf(
    value: T,
    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)

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

ParcelableSapshotMutableState.kt

@SuppressLint("BanParcelableUsage")
internal class ParcelableSnapshotMutableState<T>(
    value: T,
    policy: SnapshotMutationPolicy<T>
) : SnapshotMutableStateImpl<T>(value, policy), Parcelable {
	...
}

mutableStateOf 会创建 ParcelableSnapshotMutableState。

看 ParcelableSnapshotMutableState 源码时你可能会很疑惑:怎么 ParcelableSnapshotMutableState 的代码没有监听或修改 value 相关的实现?

实际上相关的逻辑都在父类 SnapshotMutableStateImpl:

SnapshotState.kt

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
	// 重写了 value 的 getter/setter
    @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
	...
}

在 SnapshotMutableStateImpl 看到了对数值 value 的 getter/setter 被重写了。当写的时候就是调用重写的 setter 通知 Compose 刷新。

什么是 Compose 的刷新?接下来简单了解下。

Compose 的刷新

Compose 的刷新是由三部分组成:

  • 组合(Composition,动词就是 Compose)

  • 布局

  • 绘制

Composition 组合的过程其实就是执行添加了 @Composable 注解的函数的过程,与传统 UI 的测量/布局、绘制相比多了前面第一步。

为什么要多一个组合的过程?

这是因为 用 @Composable 注解的函数并不是界面元素,而是用于生成界面的元素,所以组合过程其实是一个 “拼凑出界面内容” 的过程

我们写的 Android 代码是运行在 Android 平台,而在 Android 平台要显示内容只有一个办法,就是通过 View 系统,实际上我们写的 Composable 最终都会被装载进一个 ComposeView,而它里面又会包着一个 AndroidComposeView,而 AndroidComposeView 会用 LayoutNode 进行真正的布局绘制,这些 LayoutNode 就是在上面提到的组合过程生成的。关于这块的原理都是后话,在后续的篇章会讲解到。

StateRecord、StateObject 与订阅的关系

mutableStateOf 的订阅行为涉及了两个比较重要的类:StateRecord 和 StateObject。

SnapshotState.kt

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

可以看到 SnapshotMutableStateImpl 实现了两个接口:StateObject 和 SnapshotMutableState。

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

SnapshotMutableState 就是 MutableState 的子类,接口并没有订阅监听相关的代码,也就是说 MutableState 本身并没有订阅的功能。StateObject 会记录当前值或者说 Compose 的状态

interface StateObject {
	// 每个 StateRecord 就是 Compose 的状态
	// 链表头节点,有头节点就能访问整个链表
	val firstStateRecord: StateRecord 
	...
}

abstract class StateRecord {
	// 指向链表的下一个节点
	internal var next: StateRecord? = null 
	...
}

StateObject 有一个重要的变量 firstStateRecord,它是 StateRecord 类型,firstStateRecord 是一个链表结构,通过链表的方式链式记录状态,firstStateRecord 也是链表的头节点;StateRecord 的变量 next 是链表的下一个节点。

也就是说,StateObject 持有了 StateRecord 链表,一个 StateObject 对应多个 StateRecord,每个 StateRecord 记录当前 Compose 的状态

在这里插入图片描述

在我们认知中,值改变了,只要替换成新值就行了,Compose 为什么不提供一个变量直接把值放在 SnapshotMutableStateImpl,而是另外的用链表记录所有状态变化,把新值旧值都存了起来?

因为 在 Compose 旧值也是有用的,Compose 支持事务功能,对于什么是事务功能会在后续讲到,现在只需要记住事务功能就是支持批量更新,更新可以撤销,要撤销就要有旧值需要存旧值。

简单总结下 StateRecord 和 StateObject:

  • 一个 StateRecord 可以理解为某一时刻 Compose 当前的状态

  • StateObject 持有 StateRecord 链表即记录着 Compose 的各个状态变动

SnapshotMutableStateImpl.value 的 getter 完成订阅行为

Composable 获取数据时是调用 MutableState 的 value 的 getter:

SnapshotState.kt

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
    	// next 是 StateStateRecord
        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

	// StateStateRecord 是 StateRecord 的子类
    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
    }
	...
}

value 的 getter 是调用 next.readable(this).value 获取数值。这个操作又可以拆解为两部分:

  • next 到底是什么?

  • next.readable(this) 是什么操作?返回了什么对象?

在上个节点了解到 StateObject 持有 StateRecord 链表记录着 Compose 当前状态这个知识点,下面就能很好的理解 next 到底是什么:

SnapshotState.kt

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 }
            }
        }

	// firstStateRecord 是 StateObject 的成员,在这里重写了 getter
	// firstStateRecord 是头节点的原因是因为 next 是链表头节点
	override val firstStateRecord: StateRecord
		get() = next
	
	// StateStateRecord 是 StateRecord 的子类
	// StateRecord 有 next 成员变量指向下一个 StateRecord 节点
	// 所以可以理解为 next 就是一个链表
	private var next: StateStateRecord<T> = StateStateRecord(value)
	...
}

next 才是实际的链表头节点,firstStateRecord 的 getter 取值是 next,所以 firstStateRecord 是链表头节点。

已经知道 next 是 StateStateRecord,继续看它调用 readable() 做了什么事情:

Snapshot.kt

// next.readable(this)
// state 参数就是 SnapshotMutableStateImpl,同样它也是 StateObject
fun <T : StateRecord> T.readable(state: StateObject): T {
	val snapshot = Snapshot.current
	// snapshot.readObserver?.invoke(state) 就是实际做记录的操作,也就是订阅
	snapshot.readObserver?.invoke(state) 
	return readable(this, snapshot.id, snapshot.invalid) ?: sync {
		val syncSnapshot = Snapshot.current
		readable(this, syncSnapshot.id, syncSnapshot.invalid)
	} ?: readError()
}

snapshot.readObserver?.invoke(state) 就是实际的订阅操作,记录 MutableState 是在哪里被调用

// name 是 MutableState 对象,执行 name.value 时会调用 value 的 getter
// getter 会调用 snapshot.readObserver?.invoke(state) 记录,也就是订阅行为
// 此时就会记录该 MutableState 被这个 Composable 使用
setContent {
	Text(name.value)
}
 

这一步订阅的作用是,当 name.value 被写时,就会去找到所有订阅使用的位置然后标记为失效,等到下一帧这些失效的地方就会被刷新重新执行,更确切的说是会被 Recompose 重组

Recompose 的范围是 Composable 所在的 lambda,例如上面例子是 setContent 的 lambda 内都会 Recompose。

fun <T : StateRecord> T.readable(state: StateObject): T {
	val snapshot = Snapshot.current
	snapshot.readObserver?.invoke(state) 
	// 三参数 readable() 遍历 StateRecord 链表,找到最新的、可用的 StateRecord
	return readable(this, snapshot.id, snapshot.invalid) ?: sync {
		val syncSnapshot = Snapshot.current
		readable(this, syncSnapshot.id, syncSnapshot.invalid)
	} ?: readError()
}

完成订阅后,又调用了一个 三参数的 readable() 函数,它的作用是遍历 StateRecord 链表,找到一个最新的、可用的 StateRecord

总结下 value 的 getter 主要有两个操作:

  • 记录 MutableState 被哪些 Composable 使用,该操作是订阅行为

  • 返回一个最新的、可用的 StateRecord

简单总结下 mutableStateOf 函数完成订阅的要点:

  • mutableStateOf 返回的对象(MutableState)可以被订阅,是因为返回对象的成员变量 value 的 getter 被定制了

  • 每次调用 getter 时都会记录 MutableState 被哪些 Composable 使用,记录这个步骤就是完成订阅的行为

  • 订阅的作用是当调用 setter 时就会标记订阅位置的数值失效,要在下一帧时刷新 Compose

  • 订阅完成后,在 StateRecord 链表找到一个最新的、可用的 StateRecord 返回

我们结合一开始的例子再说明下订阅操作:

在这里插入图片描述

Snapshot 与 StateRecord 的关系

无论是订阅的时候还是接下来要讲的 value 的 setter,都有涉及到一个概念:Snapshot,也就是快照。

StateRecord 每个修改的新旧值都会被串起来形成一个链表,链表的各个节点都代表了某一时刻 Compose 的整个内部状态,StateRecord 链表上的哪些节点共属于同一个状态也有记录,用的就是 Snapshot,每个 StateRecord 都有对应的 Snapshot id

Snapshot 代表某一时刻 Compose 整体状态,一个 Snapshot 可以对应多个 StateRecord,一个 StateRecord 对应一个 Snapshot

在这里插入图片描述

有了 Snapshot 后就可以在一些变量值发生变化的时候,不必马上将 StateRecord 改动应用到内部直接显示到界面,而是可以在跑完整个 Compose 流程后把所有要改变的变量一起应用,直接拿最终的结果进行布局和绘制,性能也会好一些。Snapshot 的机制对这种批量应用改变提供了下层技术可行性的支持。

按上面的描述可能还是不好理解什么是 Snapshot,依然用一开始的例子讲解:

// 初始值:SnapshotId=1,MutableState 对应的 StateRecord 的 SnapshotId=1
// 变化:SnapshotId=2,SnapshotId=2 没有 StateRecord,从 SnapshotId=1 拿对 SnapshotId=2 还是有效的 StateRecord
val name = mutableStateOf("name")
setContent {
	Text(name.value)
}

lifecycleScope.launch {
	delay(3000)
	name.value = "vincent"
}
  • 假设现在初始值的 SnapshotId=1,MutableState 对应的 StateRecord 的 SnapshotId=1

  • 在某个时刻 SnapshotId=2,SnapshotId=2 此时没有对应的 StateRecord,但是 SnapshotId=1 的 StateRecord 对 SnapshotId=2 而言是可用有效的,那么 SnapshotId=2 就可以从 SnapshotId=1 拿那个可用有效的 StateRecord

  • 因为 SnapshotId=2 是建立在 SnapshotId=1 的基础上,这也是 SnapshotId=2 能拿 SnapshotId=1 的 StateRecord 使用的原因。

StateRecord 和 Snapshot 的关系:

  • 系统有多个 Snapshot 的时候,它们是有先后关系的,新的 Snapshot 是在上一个 Snapshot 基础上创建

  • 同一个 StateObject 的每个 StateRecord,都有它们对应的 Snapshot 的 id。StateRecord 和 Snapshot 就算不直接对应,只要 Snapshot 的 StateRecord 对另一个 Snapshot 是有效的,另一个就能取到这个 StateRecord

SnapshotMutableStateImpl.value 的 setter

SnapshotState.kt

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
        // next.withCurrent 调用的三参数的 readable() 返回一个最新的可用的 StateRecord
        set(value) = next.withCurrent {
        	// 比较两个值是否相同
            if (!policy.equivalent(it.value, value)) {
                next.overwritable(this, it) { this.value = value }
            }
        }
}

Snapshot.kt

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

@PublishedApi
internal fun <T : StateRecord> current(r: T) = 
    Snapshot.current.let { snapshot ->
        readable(r, snapshot.id, snapshot.invalid) ?: sync {
            Snapshot.current.let { syncSnapshot ->
                readable(r, syncSnapshot.id, syncSnapshot.invalid)
            }
        } ?: readError()
    }

value 的 setter 首先是调用了三参数的 readable(),上面有提到三参数的 readable() 就是获取一个最新的、可用的 StateRecord。policy.equivalent() 先判断数值是否改变了,如果有就执行 next.overwritable()。

Snapshot.kt

internal inline fun <T : StateRecord, R> T.overwritable(
    state: StateObject, // SnapshotMutableStateImpl
    candidate: T, // 最新的可用的 StateRecord
    block: T.() -> R // block 处理的赋值操作 this.value = value
): R {
    var snapshot: Snapshot = snapshotInitializer
    return sync {
    	// 获取对应当前 Snapshot 的 StateRecord
    	// overwritableRecord() 返回的是 T
    	// T 是 SnapshotMutableStateImpl 的 next 变量
    	// 所以 overwritableRecord().block() 是调用 overwritable() 的传参 block
    	// block 这个 lambda 就是修改了值
        snapshot = Snapshot.current
        this.overwritableRecord(state, snapshot, candidate).block()
    }.also {
    	// 找到读取的 StateRecord 标记为失效,在下一帧重新刷新
        notifyWrite(snapshot, state)
    }
}

next.overwritable() 会调用 overwritableRecord() 会返回一个 StateRecord,然后调用 notifyWrite()。我们先看 overwritableRecord():

internal fun <T : StateRecord> T.overwritableRecord(
    state: StateObject,
    snapshot: Snapshot,
    candidate: T
): T {
    if (snapshot.readOnly) {
        // If the snapshot is read-only, use the snapshot recordModified to report it.
        snapshot.recordModified(state)
    }
    val id = snapshot.id

    if (candidate.snapshotId == id) return candidate

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

    snapshot.recordModified(state)

    return newData
}

overwritableRecord() 的代码具体可以分成两部分看:

// 如果传参的 StateRecord 的 snapshotId 和 snapshot 的 id 一样就直接返回
val id = snapshot.id
if (candidate.snapshotId == id) return candidate

candidate 是通过三参数 readable() 获取的最新的可用的 StateRecord,在上一节点我们提及,每个 StateRecord 都有对应的 snapshotId,如果和当前 snapshotId 一样,说明这个 StateRecord 是可用的可以直接返回。

// snapshotId 不一样,返回一个新的可用的 StateRecord
// 这个 StateRecord 可能是用已有的,也有可能是新创建的
// StateRecord 和 Snapshot 就算不直接对应
// 只要 StateRecord 的 Snapshot 对另一个是有效的,另一个就能取到这个 StateRecord
val newData = newOverwritableRecord(state, snapshot)
newData.snapshotId = id

snapshot.recordModified(state)

当前的 Snapshot 是有可能没有相应的 StateRecord,因为 Snapshot 是有先后关系且之前的 Snapshot 的 StateRecord 对当前 Snapshot 是有效的,即使 StateRecord 和 Snapshot 不直接对应,也是可以返回这个 StateRecord。

@PublishedApi
internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
    snapshot.writeObserver?.invoke(state) // 通知订阅的位置标记为失效
}

最后是调用 notifyWrite(),其实就是将调用 value 的 getter 标记为失效,要在下一帧刷新。

总结下 setter 的操作主要有两个步骤:

  • overwritable() 调用 overwritableRecord() 获取当前 Snapshot 可用的 StateRecord,然后更新 StateRecord 的 value

  • 通知订阅的位置标记为失效,下一帧时刷新

Compose 的两套订阅机制

上面我们了解了 MutableState 的原理,简单说就是 getter 负责订阅,setter 负责通知标记失效,等下一帧刷新

但实际上这还并不是完整的原理,刚刚我们讲到的订阅只是其中一个订阅步骤。实际上 Compose 是有两个订阅过程的:

Snapshot.kt

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
        readable(this, syncSnapshot.id, syncSnapshot.invalid)
    } ?: readError()
}

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

Snapshot 分别使用了 readObserver 和 writeObserver,看名字就能知道 Observer 它是属于接收者,是属于订阅机制里面的被通知的对象。

实际上 readObserver 和 writeObserver 它们都属于两个不同订阅机制中的两个被通知的对象,即 readObserver 是对读进行订阅,writeObserver 是对写进行订阅,分别收到读和写的通知。所以这也侧面说明 Compose 是有两套订阅的。

Compose 中有两个订阅过程:

  • 第一个订阅发生在 Snapshot 创建的时候开始订阅,这里订阅的是 Snapshot 对读写 StateObject 对象的订阅。当 Snapshot 内部读写 StateObject 对象时,readObserver 和 writeObserver 就会分别收到相应的通知,通知某个 StateObject 被读写了

  • 第二个订阅是对每个 StateObject 的应用事件做订阅

应用事件就是让这个新值变成全局可用的,Compose 允许存在多个 Snapshot,但只能存在一个当前且全局的 Snapshot,当对一个非全局的 Snapshot 的改动应用到全局的 Snapshot 时,这个改动才算是生效的,这个过程就是所谓的应用

第二个订阅发生在 readObserver 被调用的时候,也就是调用 snapshot.readObserver?.invoke(state) 时,这个调用就是应用事件的订阅。通知发生在 StateObject 新值被应用的时候。

所以 snapshot.readObserver?.invoke(state) 既是通知又是对应用事件的订阅

// 既是对 Snapshot 订阅的读的通知
// 也是做 StateObject 新值的应用事件的订阅
snapshot.readObserver?.invoke(state)

在上面我们讲到,在变量写的时候会通过 writeObserver 把应用到变量的地方标记为失效,在下一帧刷新,其实这只是其中一种情况而已。writeObserver 订阅的是 Snapshot,而 Snapshot 主要用在 Compose 过程也就是用在组合过程,也就是说 writeObserver 是在组合过程中发生了变量值的改变,这时候它才会去标记失效

class MainActivity : ComponentActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		val test = mutableStateOf("1")
		setContent {
			Box(modifier = Modifier.clickable {
				// 没有发生在 Compose 过程,发生的写事件是不会被通知到的
				// 只有发生 Compose 组合过程中的写事件才会通知 writeObserver
				test.value = "2" 
			}) {
				// test.value 触发读过程并且记录了
				Text(test.value)
				// 触发写,会让 Text(test.value) 失效
				// Text() 所在的 lambda 会在下一帧 Recompose
				test.value = "3"
			}
		}
	}
}

上面例子中 Box() 的点击事件因为没有参与组合过程,所以在点击事件对 MutableState 写操作并不会触发 Recompose。

by 委托便捷写法

每次使用 MutableState 都要 .value 才能使用很麻烦,所以 Compose 为我们提供了简洁处理,帮我们去掉了这个操作,通过 by 将 setValue 和 getValue 都交由 mutableStateOf 委托处理。

// val name = mutableStateOf("name")
var name by mutableStateOf("name")
setContent {
	// Text(name.value)
    Text(name)
}

Compose 引入了一个扩展函数 androidx.compose.runtime.setValue 和 androidx.compose.runtime.getValue,将 MutableState 类型转换为调用的数值类型,但目前仍需要手动导入这个扩展。

总结

简单总结下 mutableStateOf 的读写原理:

  • mutableStateOf 的 value 在 getter 时,Compose 会把它读的位置记录下来

  • value 的 setter 会将读到这个变量的所有地方标记为失效

  • 当每个新值被应用的时候,Compose 也会把读到这个变量的所有地方标记为失效,标记失效的地方都会在下一帧进行刷新

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值