本篇是 Compose 系列的最后一篇文章,我们来了解一下 Compose UI 的显示与刷新原理。
UI 要显示到界面上,通常可以分为两步:设置要显示的内容、程序运行到相应的生命周期显示该内容。
1、设置显示内容
使用原生的 View 系统构建 UI 时,都是在 Activity 的 onCreate() 中调用 setContentView() 设置 Activity 的显示内容。到了 Compose 就改为调用 ComponentActivity 的扩展函数 setContent()。实际上目的都是为了设置 Activity 的根视图,也就是要显示的内容:
/**
* 将给定的可组合项合成到给定的 Activity 中。[content] 将成为给定 Activity 的根视图。
* 这大致相当于使用一个 [ComposeView] 调用 [ComponentActivity.setContentView]:
*
* setContentView(
* ComposeView(this).apply {
* setContent {
* MyComposableContent()
* }
* }
* )
*
* @param parent 父组合(parent composition)的引用,用于协调组合更新的调度
* @param content 一个声明 UI 内容的 @Composable 函数
*/
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
// 1.复用已经存在的 ComposeView:将 DecorView 中 id 为 android.R.id.content 的 ViewGroup
// 的第一个子组件转换成 ComposeView。因为最下面 setContentView() 设置的就是它。
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
// 2.existingComposeView 若已存在
if (existingComposeView != null) with(existingComposeView) {
// 此处调用的是 ComposeView 的 setContent(),因此不会形成递归
// 2.1 更新组合上下文关系
setParentCompositionContext(parent)
// 2.2 替换现有的 Compose 内容
setContent(content)
} else ComposeView(this).apply { // 3.existingComposeView 若不存在
// 3.1 设置组合上下文以及 Compose 内容
setParentCompositionContext(parent)
setContent(content)
// 3.2 生命周期配置:绑定生命周期所有者、绑定 SavedStateRegistry 等
// AppCompat 1.3+ 版本之前由于 bug 不会自动设置 Owners,所以才在这里手动设置
setOwners()
// 设置当前的 ComposeView 为 Activity 的根视图
setContentView(this, DefaultActivityContentLayoutParams)
}
}
setContent() 会将 Compose 组件设置为 Activity 的根视图,具体说来:
- 将 Compose 组件封装进 ComposeView 中,然后用 ComponentActivity 的 setContentView() 将这个 ComposeView 设置为 Activity 的根视图
- 如果已经使用 setContentView() 设置过 Activity 的根视图,那么 existingComposeView 就能获取到这个 ComposeView,复用可以避免重复创建从而优化性能
接下来我们梳理一下 setContent() 的内容细节。
首先解释一下 existingComposeView。
我们都知道 Activity 的 DecorView 中的 android.R.id.content 是一个 ViewGroup(也就是上图紫色的 R.layout.xxx 部分),用来展示 Activity 的根视图。对这个 ViewGroup 调用 getChildAt(0) 就是去拿这个 ViewGroup 内的根组件,这个根组件是在最下方通过 setContentView() 设置的,就是将 ComposeView 按照 DefaultActivityContentLayoutParams 添加到 ViewGroup 内。因此,第一次进入 setContent() 时,由于还没有调用 setContentView() 设置过 Activity 的根视图,此时 的 existingComposeView 就是 null,会执行注释中第 3 步的流程。在这一次调用过 setContentView() 之后,existingComposeView 就不是 null 了。后续如果发生配置变化(如屏幕旋转),重新创建 Activity 再次进入 setContent() 时,existingComposeView 可以被复用而无需重新创建 ComposeView。
然后我们看在 2、3 两步都调用了的两个函数 —— ComposeView 的 setParentCompositionContext() 和 setContent()。
setParentCompositionContext() 用于设置父组合上下文(用于管理 Composition 的作用域和生命周期),是 ComposeView 的父类 AbstractComposeView 中的函数:
/**
* 设置应该作为此视图的组合(view's composition)的父级的 CompositionContext。如果 parent 为 null,
* 则将自动从视图所依附到的窗口(the window the view is attached to)确定。
*/
fun setParentCompositionContext(parent: CompositionContext?) {
parentContext = parent
}
CompositionContext 用于连接父组合与子组合,参数的 parent 是由 setContent() 的第一个参数传到这里的。由于调用 setContent() 时通常不会给 parent 参数赋值,因此它会取默认值 null,所以这里给 parentContext 赋的就是 null。至于 parentContext 属性的具体内容,我们暂时还用不到,因此暂时搁置。
然后我们再看 ComposeView 的 setContent(),其作用是设置 Compose UI 内容:
// 保存页面要展示的 Composable 组件函数的状态
private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
/**
* 设置此视图的 Jetpack Compose UI 内容。当视图附加到窗口或调用 createComposition 时,将发生
* 初始的组合(initial composition),以先到者为准。
* @param content 就是在 Activity 的 onCreate() 中调用的 setContent() 的尾随 lambda,页面内容
*/
fun setContent(content: @Composable () -> Unit) {
// 这个变量表示是否应该在附加到窗口时创建新的组合,后面会用到
shouldCreateCompositionOnAttachedToWindow = true
// 将参数传入的表示 UI 内容的 Composable 函数保存到 content 中
this.content.value = content
// 我们现在看的是 onCreate() 中的流程,还没到 onAttachedToWindow(),因此此时
// isAttachedToWindow 为 false,不会进入 if,但是 createComposition() 内的主要逻辑
// ensureCompositionCreated() 会在附加到 Window 的过程 onAttachedToWindow() 中被调用
if (isAttachedToWindow) {
createComposition()
}
}
setContent() 流程走完了,按照当前流程就是设置了一个布尔值,保存了 Activity 要显示的 Composable 组件函数到 content 属性中,并没有看出如何显示这个 content 的。这是因为 setContent() 只负责设置 Activity 根视图的 ComposeView,而显示需要等到被设置为 Activity 根视图的 ComposeView 的生命周期走到 onAttachedToWindow() 时才会显示。
2、创建组合
ComposeView 是 AbstractComposeView 这个 ViewGroup 的子类,那也就是 View 的子类。一个 View 只有在生命周期走到 onAttachedToWindow(),也就是其附加到窗口时才会显示:
override fun onAttachedToWindow() {
super.onAttachedToWindow()
// 记录与当前页面绑定的 Window
previousAttachedWindowToken = windowToken
if (shouldCreateCompositionOnAttachedToWindow) {
// 确保 Composition 被创建
ensureCompositionCreated()
}
}
shouldCreateCompositionOnAttachedToWindow 表示是否应该在走到 onAttachedToWindow() 时创建 Composition,在前面执行 ComposeView 的 setContent() 时已经被设为 true 了,所以这里可以进入 if 执行 ensureCompositionCreated():
private fun ensureCompositionCreated() {
// 还没执行过组合过程,因此 composition 为 null
if (composition == null) {
try {
// 标记,是否正在创建组合,防止重入
creatingComposition = true
// 进行组合过程并返回组合结果
composition = setContent(resolveParentCompositionContext()) {
Content()
}
} finally {
// 创建完组合,清除标记
creatingComposition = false
}
}
}
这里调用的是父类 AbstractComposeView 的 setContent 函数(双参),而不是此前看过的子类的 ComposeView 的 setContent 函数(单参,并且父类中也没办法调用子类的函数,除非就是子类对象调用了一个父子都有的函数),目的就是为了创建一个组合对象 Composition:
internal fun AbstractComposeView.setContent(
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
// 1.启动全局快照监听
GlobalSnapshotManager.ensureStarted()
// 2.视图复用逻辑
val composeView =
if (childCount > 0) {
getChildAt(0) as? AndroidComposeView
} else {
removeAllViews(); null
} ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
// 3.实际创建组合
return doSetContent(composeView, parent, content)
}
创建组合的过程主要做了三件事:
- 启动全局快照监听
- 尝试复用 AndroidComposeView,如没有则创建一个,并将其以 DefaultLayoutParams(宽高都 Wrap) add() 到 ComposeView 中
- 调用 doSetContent() 创建 Composition 对象并返回
在看 setContent() 的具体内容之前,先看它的两个参数的情况。
2.1 setContent() 的参数
调用端在调用 setContent() 时给它的两个参数传的是 resolveParentCompositionContext() 和 { Content() },分别追这两个参数的代码。
resolveParentCompositionContext()
resolveParentCompositionContext() 通过四级递进策略解析父组合的上下文,确保 Compose 组件能正确接入 Android 视图树的上下文体系:
/**
* 确定正确的 [CompositionContext] 作为当前视图组合的父级上下文。
* 该方法会缓存查找到的上下文供后续使用(详见 [cachedViewTreeCompositionContext])。
*
* 若存在 [cachedViewTreeCompositionContext],但 [findViewTreeCompositionContext] 无法找到
* 父级上下文,则优先使用已缓存的上下文(如果有效),而不是直接使用 [windowRecomposer]
* (因为后者可能延迟创建 recomposer)。
*
* 当视图被重新附加到同一窗口,且 [findViewTreeCompositionContext] 无法找到 [windowRecomposer]
* 提供的上下文时,可能出现这种情况:视图处于某个动画覆盖层(如消失动画),此时仍需保持组合/重组能力,
* 避免新建 recomposer,同时确保能定位视图树依赖。
*/
private fun resolveParentCompositionContext() = parentContext
?: findViewTreeCompositionContext()?.cacheIfAlive()
?: cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive }
?: windowRecomposer.cacheIfAlive()
第一优先级是显式指定的父级上下文 parentContext,在看 ComponentActivity.setContent() 的流程时出现过,在 setParentCompositionContext() 内被赋值,通常是 null。
第二优先级是 findViewTreeCompositionContext() 去视图树进行实时查找,找到距当前视图最近的 CompositionContext:
/**
* 返回此视图层级结构中当前节点的父组合上下文(parent CompositionContext),如果未找到则返回 null。
* 如需获取或设置特定视图的父组合上下文,请参阅 compositionContext。
*/
fun View.findViewTreeCompositionContext(): CompositionContext? {
// 1.先看自己的 compositionContext
var found: CompositionContext? = compositionContext
if (found != null) return found
// 2.如果自己的 compositionContext 为 null,就向上找父级的 compositionContext
var parent: ViewParent? = parent
while (found == null && parent is View) {
found = parent.compositionContext
parent = parent.getParent()
}
return found
}
当前 View 的 compositionContext 与父级组件的 compositionContext 都是 View 的扩展属性:
/**
* 这个属性表示视图层级中当前视图及其子视图使用的 [CompositionContext](组合上下文)。
* 设置为非 null 值可为子视图的组合提供自定义上下文;设置为 null 则回退到使用祖先视图提供的上下文。
*/
var View.compositionContext: CompositionContext?
get() = getTag(R.id.androidx_compose_ui_view_composition_context) as? CompositionContext
set(value) {
setTag(R.id.androidx_compose_ui_view_composition_context, value)
}
通常不会给 View 设置 id 为 androidx_compose_ui_view_composition_context 的 Tag,所以这一步的 compositionContext 一般为 null。
但假如 findViewTreeCompositionContext() 找到了 CompositionContext,那么就执行 cacheIfAlive():
/**
* 如果这个 CompositionContext 处于活跃状态,将它缓存到 cachedViewTreeCompositionContext 中同时
* 返回自身
*/
private fun CompositionContext.cacheIfAlive(): CompositionContext = also { context ->
context.takeIf { it.isAlive }
?.let { cachedViewTreeCompositionContext = WeakReference(it) }
}
当 CompositionContext 处于活跃状态时,将其缓存到 cachedViewTreeCompositionContext 中同时返回它。
第三优先级正是从 cachedViewTreeCompositionContext 这个弱引用中去获取 CompositionContext,如果其处于活动状态就使用它。
第四优先级是检查 windowRecomposer 并在它处于活跃状态时缓存它,它也是 View 的扩展属性:
/**
* 获取或延迟创建与此视图所在窗口关联的 [Recomposer]。调用此属性时,视图必须已附加到窗口,
* 且窗口的根视图已通过 [findViewTreeLifecycleOwner] 注册了 [LifecycleOwner]。
*/
@OptIn(InternalComposeUiApi::class)
internal val View.windowRecomposer: Recomposer
get() {
check(isAttachedToWindow) {
"Cannot locate windowRecomposer; View $this is not attached to a window"
}
// 获取窗口内容根视图
val rootView = contentChild
return when (val rootParentRef = rootView.compositionContext) {
// 无父级上下文时创建并安装 Recomposer
null -> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)
// 已有 Recomposer 直接复用
is Recomposer -> rootParentRef
// 类型不匹配时抛出错误
else -> error("root viewTreeParentCompositionContext is not a Recomposer")
}
}
contentChild 就是 android.R.id.content 对应的 ViewGroup 的直接子视图,也就是 ComponentActivity.setContent() 中看到的那个 ComposeView —— existingComposeView:
/**
* 查找当前视图的内容子视图(content child)。内容子视图指以下两种之一:
* 1. ID 为 [android.R.id.content] 的视图的直接子视图(即通过 Activity 或 Dialog
* 的 setContentView 设置的内容视图)
* 2. 窗口的根视图
* 不使用 [View.getRootView] 的原因是:Android 框架会在 Activity 重建时重用窗口装饰视图(DecorView)。
* 由于窗口重组器(window recomposer)与宿主 Activity 的生命周期绑定,当 Activity 实例重建时,
* 需要让旧的 recomposer 关闭,并为新 Activity 实例创建新的 recomposer。
*/
private val View.contentChild: View
get() {
var self: View = this
var parent: ViewParent? = self.parent
while (parent is View) {
if (parent.id == android.R.id.content) return self
self = parent
parent = self.parent
}
return self
}
将 contentChild 属性赋值给 rootView 后,根据 rootView.compositionContext 决定走哪一条分支。前面提过,现在所有 View 的 CompositionContext 都是 null,因此要调用 WindowRecomposerPolicy.createAndInstallWindowRecomposer():
@InternalComposeUiApi
object WindowRecomposerPolicy {
private val factory = AtomicReference<WindowRecomposerFactory>(
WindowRecomposerFactory.LifecycleAware
)
internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {
val newRecomposer = factory.get().createRecomposer(rootView)
rootView.compositionContext = newRecomposer
// 界面退出后做清理收尾工作的代码,省略...
return newRecomposer
}
}
该函数通过 WindowRecomposerFactory 工厂的 createRecomposer() 创建了 CompositionContext 的子类对象 Recomposer,并将该对象赋值给 rootView 的 compositionContext 属性。至此,rootView 的 compositionContext 终于不为空了。
下面我们要看一下 createRecomposer() 是如何创建 CompositionContext 的,点进函数源码发现是 WindowRecomposerFactory 的接口函数:
/**
* 用于创建 Android 窗口作用域重组器(Recomposer)的工厂接口。详见 createRecomposer。
*/
@InternalComposeUiApi
fun interface WindowRecomposerFactory {
/**
* 为指定窗口创建 [Recomposer],[windowRootView] 是该窗口视图层级的根视图。
* 工厂需定义重组器的关闭策略(通过 [Recomposer.cancel])。
* [windowRootView] 将持有返回的 [Recomposer] 的强引用,直到其关闭后完成 [join]。
*/
fun createRecomposer(windowRootView: View): Recomposer
companion object {
/**
* 创建生命周期感知的 [Recomposer] 的工厂实现。
*
* 返回的 [Recomposer] 将绑定到视图层级根视图的 [LifecycleOwner],
* 并在窗口 UI 线程的 [AndroidUiDispatcher.CurrentThread] 上运行重组和组合副作用。
* 关联的 [MonotonicFrameClock] 仅在生命周期至少为 [STARTED] 时产生帧,
* 使得动画等使用帧时钟的 API 在窗口不可见时暂停。
*/
@OptIn(ExperimentalComposeUiApi::class)
val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView ->
rootView.createLifecycleAwareWindowRecomposer()
}
}
}
此时就要看 factory.get() 拿到的是什么对象,通过 factory 的定义可以确定 get() 得到的是一个正是该接口伴生对象内定义的 LifecycleAware,createLifecycleAwareWindowRecomposer() 会返回一个已经准备好(界面刷新)的工具 Recomposer:
@ExperimentalComposeUiApi
fun View.createLifecycleAwareWindowRecomposer(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
lifecycle: Lifecycle? = null
): Recomposer {
val baseContext = if (coroutineContext[ContinuationInterceptor] == null ||
coroutineContext[MonotonicFrameClock] == null
) {
AndroidUiDispatcher.CurrentThread + coroutineContext
} else coroutineContext
val pausableClock = baseContext[MonotonicFrameClock]?.let {
PausableMonotonicFrameClock(it).apply { pause() }
}
...
val motionDurationScale = baseContext[MotionDurationScale] ?: MotionDurationScaleImpl().also {
systemDurationScaleSettingConsumer = it
}
val contextWithClockAndMotionScale =
baseContext + (pausableClock ?: EmptyCoroutineContext) + motionDurationScale
val recomposer = Recomposer(contextWithClockAndMotionScale)
...
return recomposer
}
所以,resolveParentCompositionContext() 最终返回的是一个用于进行重组刷新的工具 Recomposer。
Content()
再看 setContent() 的第二个参数,传的是 { AbstractComposeView.Content() }:
@Composable
@UiComposable
abstract fun Content()
在它的子类 ComposeView 中的实现就是调用此前保存在 content 属性中的函数:
private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
@Composable
override fun Content() {
content.value?.invoke()
}
把 Content() 放入 lambda 表达式中作为 setContent() 的第二个参数,执行该 lambda 就是执行 ComponentActivity.setContent() 传入的 Composable 组件函数。它与第一个参数 resolveParentCompositionContext() 一样,都会传入第 3 步的 doSetContent() 中。接下来我们就开始介绍 setContent() 中的 3 个步骤。
2.2 启动快照全局监听
先看 setContent() 的第 1 步,调用 GlobalSnapshotManager 的 ensureStarted() 启动全局快照监听:
/**
* 平台特定的全局快照状态写入监听机制,用于调度定期发送快照应用通知。
* 该机制需保持平台特定性,与特定平台和框架目标的线程模型及更新机制紧密相关。
*
* 特定平台/框架的组合引导机制应在初始化时调用 [ensureStarted] 方法,
* 以启用全局快照通知。在 Android 平台上,这些通知始终通过 [AndroidUiDispatcher.Main] 发送,
* 其他平台可采用不同的策略。
*/
internal object GlobalSnapshotManager {
// 原子布尔值确保单次初始化
private val started = AtomicBoolean(false)
// 启动全局快照监听
fun ensureStarted() {
if (started.compareAndSet(false, true)) {
// 创建合并型 Channel(仅保留最新事件)
val channel = Channel<Unit>(Channel.CONFLATED)
// 在 Android 主线程创建协程作用域
CoroutineScope(AndroidUiDispatcher.Main).launch {
// 持续消费 Channel 事件
channel.consumeEach {
// 发送快照应用通知
Snapshot.sendApplyNotifications()
}
}
// 注册全局写入观察者
Snapshot.registerGlobalWriteObserver {
// 触发 Channel 发送事件
channel.trySend(Unit)
}
}
}
}
Snapshot 系统是用来管理 Compose 组件依赖的状态的,它会在这些状态发生变化时自动感知到它们的变化,并把这些变化应用到界面中,保证使用这些状态最新的值去及时的刷新界面。具体的做法就是在上面的代码中,Snapshot 通过 registerGlobalWriteObserver() 注册了全局的写值观察者,这样 Snapshot 在监听到组件依赖的变量发生变化时,就会执行 Channel 的 trySend() 去生产事件。消费端是在 Android 主线程启动的协程中通过 consumeEach() 持续地接收事件,调用 Snapshot 的 sendApplyNotifications() 通知有状态发生了更新。
此外,Snapshot 还支持多线程,可以在多个线程中去更新 Compose 组件依赖的状态,而原生只能在主线程中更新 UI。
通知状态的变化
正如上面所说,当状态值发生变化时,会在 Channel 消费事件时调用 Snapshot 的 sendApplyNotifications() 发送通知:
sealed class Snapshot {
// 发生了变化的状态集合,不为 null 时说明有状态更新了
override var modified: MutableSet<StateObject>? = null
companion object {
fun sendApplyNotifications() {
val changes = sync {
// currentGlobalSnapshot 是原子引用 AtomicReference,里面包着 GlobalSnapshot
currentGlobalSnapshot.get().modified?.isNotEmpty() == true
}
if (changes)
advanceGlobalSnapshot()
}
}
}
当组件依赖的状态有变化时,会调用 advanceGlobalSnapshot():
private fun advanceGlobalSnapshot() = advanceGlobalSnapshot { }
private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
// 原子获取当前全局快照 GlobalSnapshot
var previousGlobalSnapshot = snapshotInitializer as GlobalSnapshot
val result = sync {
previousGlobalSnapshot = currentGlobalSnapshot.get()
// 创建新快照并执行回调
takeNewGlobalSnapshot(previousGlobalSnapshot, block)
}
// 1.检查是否有修改的状态
val modified = previousGlobalSnapshot.modified
if (modified != null) {
// 2.获取观察者列表的快照副本
val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }
// 3.遍历观察者进行状态的应用
// 把界面中用到的发生变化的状态进行新值的应用。确切地说,把界面中使用到的发生状态变化的
// 部分标记为失效,这样失效的部分就会发生重组,然后再发生布局和绘制,完成整个页面的刷新。
// 只标记发生变化的部分是为了节省性能,只刷新应该刷新的地方保证耗时最短,刷新效率最高
observers.fastForEach { observer ->
observer(modified, previousGlobalSnapshot)
}
}
// 清理无效记录
sync {
modified?.forEach(::overwriteUnusedRecordsLocked)
}
return result
}
观察者列表 applyObservers 是在 registerApplyObserver() 中被添加的:
// 还是在 Snapshot 的伴生对象中
fun registerApplyObserver(observer: (Set<Any>, Snapshot) -> Unit): ObserverHandle {
// Ensure observer does not see changes before this call.
advanceGlobalSnapshot(emptyLambda)
sync {
// 向 applyObservers 添加监听者
applyObservers.add(observer)
}
return ObserverHandle {
sync {
applyObservers.remove(observer)
}
}
}
registerApplyObserver() 在 Recomposer 的 recompositionRunner() 中被调用:
private suspend fun recompositionRunner(
block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
) {
withContext(broadcastFrameClock) {
...
// 注册回调,收到回调之后,通过 resume() 开始执行重组工作
val unregisterApplyObserver = Snapshot.registerApplyObserver { changed, _ ->
synchronized(stateLock) {
if (_state.value >= State.Idle) {
snapshotInvalidations.addAll(changed)
deriveStateLocked()
} else null
}?.resume(Unit)
}
...
}
}
总结一下上述过程:
- Snapshot 快照系统通过 registerGlobalWriteObserver() 设置全局监听,当有状态进行写行为时,就会触发监听回调让 Channel 生产事件
- Channel 消费端在得知状态发生变化后,执行 Snapshot 的 sendApplyNotifications() 通知状态发生了变化,触发重组
上述过程利用 Channel 的特性对重组触发次数做了优化,一帧中只会触发一或两次重组,从而避免导致高频修改引发高频刷新的性能问题。
知识贯通
这里要插入一点,因为我们刚刚看到了 Snapshot 是如何注册对 State 进行全局写监听的,所以我们要回到起点,看看当 State 发生变化时,Snapshot 会做怎样的处理。
通过 State 的 set(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 }
}
}
next 是 StateStateRecord,它的 overwritable() 会提供一个可写的 StateRecord:
/**
* 对给定的状态记录调用 [block] 函数,并为其提供一个可写的状态记录。假定这是针对状态对象中的
* 第一个状态记录调用的。若该记录是在当前可变快照中创建的,则视为可写。此方法仅应在记录的
* 所有字段将被完全覆盖时使用(例如记录只有一个字段且该字段将被写入)。
*
* 警告:如果调用者未覆盖状态记录中的所有字段,将导致对象状态不一致,未写入的字段几乎肯定会处于
* 错误状态。若 [block] 可能不会写入所有字段,请改用 [writable] 方法。
*
* @param state 包含此记录的状态对象
* @param candidate 由 [withCurrent] 返回的快照记录的当前候选记录
* @param block 将修改记录所有字段的操作块
*/
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)
}
}
首先 snapshot 被初始化为一个 GlobalSnapshot:
internal val snapshotInitializer: Snapshot = currentGlobalSnapshot.get()
private val currentGlobalSnapshot = AtomicReference(
GlobalSnapshot(
id = nextSnapshotId++,
invalid = SnapshotIdSet.EMPTY
).also {
openSnapshots = openSnapshots.set(it.id)
}
)
然后在 sync 内对其进行原子性覆盖,覆盖为当前线程快照:
// Snapshot 的伴生对象
companion object {
// 返回线程的活动快照。如果没有活动状态的线程快照,则使用当前的全局快照
val current get() = currentSnapshot()
}
internal fun currentSnapshot(): Snapshot =
threadSnapshot.get() ?: currentGlobalSnapshot.get()
全局快照 GlobalSnapshot 继承自 MutableSnapshot,它的第 4 个参数 writeObserver 就是对 State 的写监听:
internal class GlobalSnapshot(id: Int, invalid: SnapshotIdSet) :
MutableSnapshot(
id, invalid, null,
// writeObserver
sync {
(
if (globalWriteObservers.isNotEmpty()) {
globalWriteObservers.toMutableList()
} else null
)?.let {
it.singleOrNull() ?: { state: Any ->
it.fastForEach { it(state) }
}
}
}
)
overwritable() 内执行 notifyWrite() 时就会调用这个 writeObserver 的函数内容:
internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
snapshot.writeObserver?.invoke(state)
}
2.3 生成 AndroidComposeView
再看 setContent() 的第 2 部分:
val composeView =
if (childCount > 0) {
getChildAt(0) as? AndroidComposeView
} else {
removeAllViews(); null
} ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
生成一个 AndroidComposeView 并将其添加到当前的 ComposeView 中,形成一个 ComposeView 包含 AndroidComposeView 的结构。实际上,AndroidComposeView 才是负责显示与触摸反馈(真正干活的)的那个 View。
2.4 创建 Composition 对象
setContent() 的最后一部分是 doSetContent():
private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
...
val original = Composition(UiApplier(owner.root), parent)
val wrapped = owner.view.getTag(R.id.wrapped_composition_tag)
as? WrappedComposition
?: WrappedComposition(owner, original).also {
owner.view.setTag(R.id.wrapped_composition_tag, it)
}
wrapped.setContent(content)
return wrapped
}
doSetContent() 主要做了三件事:
- 创建一个 Composition 对象:传入 Composition 要使用的 Applier 与父组件的 CompositionContext,得到 CompositionImpl 类型的 original
- 获取 WrappedComposition 对象 wrapped:取 AndroidComposeView 的 id 为 wrapped_composition_tag 的 Tag,如果获取不到就用 WrappedComposition 的构造函数将 AndroidComposeView 与 CompositionImpl 包装起来,并把新生成的 WrappedComposition 设置给自己的 Tag
- 调用 WrappedComposition 的 setContent() 设置要显示的 Composable 组件函数
调用 WrappedComposition 的 setContent():
// 本流程中传入的 original 的实际类型是 CompositionImpl
private class WrappedComposition(
val owner: AndroidComposeView,
val original: Composition
) : Composition, LifecycleEventObserver {
private var disposed = false
private var addedToLifecycle: Lifecycle? = null
private var lastContent: @Composable () -> Unit = {}
override fun setContent(content: @Composable () -> Unit) {
// 1.对 AndroidComposeView 设置监听,当 ViewTreeOwners 可用时回调
owner.setOnViewTreeOwnersAvailable {
if (!disposed) {
val lifecycle = it.lifecycleOwner.lifecycle
// 将要显示的 Composable 组件内容保存到 lastContent 中
lastContent = content
// 2.先进入 if,再进入 else if
if (addedToLifecycle == null) {
addedToLifecycle = lifecycle
// this will call ON_CREATE synchronously if we already created
lifecycle.addObserver(this)
} else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
// original 是 CompositionImpl
original.setContent {
...
}
}
}
}
}
}
setContent() 主要有两点:
- AndroidComposeView.setOnViewTreeOwnersAvailable() 设置 ViewTreeOwners 可用的监听,当 AndroidComposeView 的 viewTreeOwners 属性不为空时回调,该属性在 onAttachedToWindow() 内被赋值,所以可以大致认为是对 onAttachedToWindow() 的监听
- addedToLifecycle 初始为 null,那么首先会进入 if 条件,为当前 AndroidComposeView 的 lifecycle 添加监听,调用 addObserver() 后生命周期满足 else if 的条件,因此又会进入 else if 执行 Composition 的 setContent()
Composition 的 setContent():
override fun setContent(content: @Composable () -> Unit) {
check(!disposed) { "The composition is disposed" }
// 将参数的 Composable 函数保存到成员属性 composable 中
this.composable = content
// parent 的声明类型是 CompositionContext,实际类型是 2.1 节中的
// resolveParentCompositionContext() 返回值 Recomposer
parent.composeInitial(this, composable)
}
看 Recomposer 的 composeInitial():
// Compose 运行时中执行初始组合(Initial Composition)的核心方法
internal override fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit
) {
// 记录组合前的状态
val composerWasComposing = composition.isComposing
try {
// 核心组合执行
composing(composition, null) {
composition.composeContent(content)
}
} catch (e: Exception) {
// 错误处理
processCompositionError(e, composition, recoverable = true)
return
}
// 快照系统通知
if (!composerWasComposing) {
Snapshot.notifyObjectsInitialized()
}
// 组合实例注册
synchronized(stateLock) {
if (_state.value > State.ShuttingDown) {
if (composition !in knownCompositions) {
knownCompositions += composition
}
}
}
...
// 应用变更
try {
composition.applyChanges()
composition.applyLateChanges()
} catch (e: Exception) {
processCompositionError(e)
return
}
// 二次快照通知
if (!composerWasComposing) {
// Ensure that any state objects created during applyChanges are seen as changed
// if modified after this call.
Snapshot.notifyObjectsInitialized()
}
}
看核心组合执行过程,在 composing() 的尾随 lambda 中执行 composition.composeContent(),就有调用链 CompositionImpl.composeContent() -> ComposerImpl.composeContent() -> ComposerImpl.doCompose():
private fun doCompose(
invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
content: (@Composable () -> Unit)?
) {
runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
trace("Compose:recompose") {
...
try {
...
observeDerivedStateRecalculations(
start = {
childrenComposing++
},
done = {
childrenComposing--
},
) {
if (content != null) {
startGroup(invocationKey, invocation)
// 关键代码
invokeComposable(this, content)
endGroup()
}
}
...
}
}
}
invokeComposable() 执行的是 ActualJvm.jvm.kt 中的函数:
internal actual fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
@Suppress("UNCHECKED_CAST")
val realFn = composable as Function2<Composer, Int, Unit>
realFn(composer, 1)
}
composable 参数是一个空参的 Composable 函数,它被强转为 Function2,是因为 Compose 编译器插件会对所有 @Composable 函数添加两个参数。最后调用转换成 Function2 的 realFn(),也就是执行 lambda 内的内容了。
以上就是 setContent() 的内容,它并没有对界面的显示做任何工作,它所做的就是布置好各种监听器,以便变量发生变化时触发界面刷新。
3、组件显示
真正负责显示的是各个组件 Composable 函数内的通用函数 Layout():
@UiComposable
@Composable inline fun Layout(
content: @Composable @UiComposable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val viewConfiguration = LocalViewConfiguration.current
ReusableComposeNode<ComposeUiNode, Applier<Any>>(
factory = ComposeUiNode.Constructor,
// 这里预先设置要做的处理策略
update = {
set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
set(density, ComposeUiNode.SetDensity)
set(layoutDirection, ComposeUiNode.SetLayoutDirection)
set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
},
skippableUpdate = materializerOf(modifier),
content = content
)
}
ReusableComposeNode 内会执行 Composer 的 createNode() 创建 LayoutNode 节点:
@Composable @ExplicitGroupsComposable
inline fun <T, reified E : Applier<*>> ReusableComposeNode(
noinline factory: () -> T,
update: @DisallowComposableCalls Updater<T>.() -> Unit,
noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
content: @Composable () -> Unit
) {
if (currentComposer.applier !is E) invalidApplier()
currentComposer.startReusableNode()
if (currentComposer.inserting) {
// 创建 LayoutNode 节点
currentComposer.createNode(factory)
} else {
currentComposer.useNode()
}
// 更新 LayoutNode 节点
Updater<T>(currentComposer).update()
SkippableUpdater<T>(currentComposer).skippableUpdate()
currentComposer.startReplaceableGroup(0x7ab4aae9)
content()
currentComposer.endReplaceableGroup()
currentComposer.endNode()
}
createNode() 会执行 factory 参数生成 LayoutNode,并把该节点插进 LayoutNode 树中:
// ComposerImpl:
@Suppress("UNUSED")
override fun <T> createNode(factory: () -> T) {
validateNodeExpected()
runtimeCheck(inserting) { "createNode() can only be called when inserting" }
val insertIndex = nodeIndexStack.peek()
val groupAnchor = writer.anchor(writer.parent)
groupNodeCount++
recordFixup { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
// 创建 LayoutNode,看参数来源,是 ReusableComposeNode() 的 factory 参数
val node = factory()
// LayoutNode 装进 SlotTable 中
slots.updateNode(groupAnchor, node)
@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<T>
nodeApplier.insertTopDown(insertIndex, node)
applier.down(node)
}
recordInsertUpFixup { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
val nodeToInsert = slots.node(groupAnchor)
applier.up()
@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<Any?>
// 将 LayoutNode 节点插进 LayoutNode 树中(树是组合的最终结果)
nodeApplier.insertBottomUp(insertIndex, nodeToInsert)
}
}
这样就是每个组件在执行到其内部的 Layout() 时都会将这个组件生成为一个 LayoutNode 并构建 LayoutNode 树。
SlotTable 是一种数据结构,用于存储 Compose 的 LayoutNode 树以及这个树上用到的变量。在显示界面时,是用不到 SlotTable 的。它在最底层是用一维数组实现了 LayoutNode 树的存储,而对于各个嵌套的 LayoutNode 之间是使用链表将它们连接起来的。使用它的目的是为了提升性能。