19.5 Compose CompositionContext 和 再谈 CompositionLocal
CompositionContext
终于可以说说 CompositionContext 了,放在最后是因为这东西没法上来直接说,需要我们前面的累积。我们来看下这个类的介绍
/**
* A [CompositionContext] is an opaque type that is used to logically "link" two compositions
* together. The [CompositionContext] instance represents a reference to the "parent" composition
* in a specific position of that composition's tree, and the instance can then be given to a new
* "child" composition. This reference ensures that invalidations and [CompositionLocal]s flow
* logically through the two compositions as if they were not separate.
*
* The "parent" of a root composition is a [Recomposer].
*
* @see rememberCompositionContext
*/
@OptIn(InternalComposeApi::class)
abstract class CompositionContext internal constructor()
总结一下
1. “子” CompositionContext 可以将两个 Composition 逻辑上链接在一起,产生逻辑上的父子关系。
CompositionContext 保存在 “父” Composition 的 SlotTable 中。
这个“子” CompositionContext 保证了两个 Composition 使用同一个 CompositionLocalMap 和 invalidations 逻辑。
2. 根 Composition 的 parentContext 就是 Activity#setContent() 时生成的 Recomposer 。
子 Composition 的 parentContext 使用 rememberCompositionContext() 方法生成。
Composition 逻辑父子关系中 CompositionContext 的类型用来判断 Composition 是不是最上层的“父” Composition。
internal class CompositionImpl(private val parent: CompositionContext){
val isRoot: Boolean = parent is Recomposer
}
CompositionContext 就两个实现类
CompositionContext
|-- Recomposer 根
|-- ComposerImpl.CompositionContextImpl 子
Recomposer 前面的章节已经介绍过了,这里我们来看 CompositionContextImpl
ComposerImpl.CompositionContextImpl
CompositionContextImpl 使用 rememberCompositionContext() 方法生成,这个方法在 Popup() 中有使用
``` @Composable fun Popup() { val parentComposition = rememberCompositionContext() val popupLayout = remember { PopupLayout().apply { setContent(parentComposition) { } } } }
internal class PopupLayout(){ fun setContent(parent: CompositionContext, content: @Composable () -> Unit) { setParentCompositionContext(parent) this.content = content shouldCreateCompositionOnAttachedToWindow = true } } ``` 由源码可见 PopupLayout 的 parentContext 是 rememberCompositionContext() 方法生成的,而不是复用 ComposeView 在初始组合流程中生成的 Recomposer。
``` @Composable fun rememberCompositionContext(): CompositionContext { return currentComposer.buildContext() }
internal class ComposerImpl( override fun buildContext(): CompositionContext { startGroup(referenceKey, reference) if (inserting) writer.markGroup()
var holder = nextSlot() as? CompositionContextHolder if (holder == null) { //创建 CompositionContextHolder 和 CompositionContextImpl 对象 //holder.ref 是 CompositionContextImpl 对象 holder = CompositionContextHolder( CompositionContextImpl( compoundKeyHash, forceRecomposeScopes ) ) //holder 保存到“父” Composition 的 SlotTable 中 updateValue(holder) } //将父 Composition Slot中的 CompositionLocalMap 保存到 //CompositionContextImpl.compositionLocalScope 中 holder.ref.updateCompositionLocalScope(currentCompositionLocalScope()) endGroup() //返回 CompositionContextImpl 对象 return holder.ref } } ``` 生成一个被 CompositionContextHolder 持有的 CompositionContextImpl 对象 ,
将 holder 保存到“父” Composition 的 SlotTable 中
传递 CompositionLocalMap
返回 CompositionContextHolder 持有的 CompositionContextImpl 对象
随后这个 CompositionContextImpl 作为 PopupLayout 的 parentContext 参与到 PopupLayout 的初始组合和重组中。
CompositionContextHolder 结构简单,实现了 RememberObserver ,这是一个很重要的接口,我们下章来学习它。
private class CompositionContextHolder(
val ref: ComposerImpl.CompositionContextImpl
) : RememberObserver {
override fun onRemembered() { }
override fun onAbandoned() {
ref.dispose()
}
override fun onForgotten() {
ref.dispose()
}
}
CompositionContext 类注释中说:
“子” CompositionContext 保证了两个 Composition 使用同一个 CompositionLocalMap 和 invalidations 逻辑。
同一个 CompositionLocalMap,buildContext() 源码有体现 ,具体后面会分析。
holder.ref.updateCompositionLocalScope(currentCompositionLocalScope())
同一个 invalidations 逻辑,看 CompositionContextImpl 源码就明白了。
``` //源码不完整 private inner class CompositionContextImpl( override val compoundHashKey: Int, override val collectingParameterInformation: Boolean ) : CompositionContext() { override val effectCoroutineContext: CoroutineContext get() = parentContext.effectCoroutineContext
override fun composeInitial( composition: ControlledComposition, content: @Composable () -> Unit ) { parentContext.composeInitial(composition, content) }
override fun invalidate(composition: ControlledComposition) { //先处理父 composition 再处理自己 parentContext.invalidate([email protected]) parentContext.invalidate(composition) } } ``` “子” Composition 中的 invalidations 逻辑 最后都是由“根” Composition 中的 Recomposer 来发起的。
再谈 CompositionLocal
14.1 的时候介绍过 CompositionLocal ,只说了 Android 默认添加的 CompositionLocal 和 如何自定义 CompositionLocal,说倒 CompositionLocalProviderI() 就结束了。
这里我们再深入了解
- CompositionLocal 如何解析
- CompositionLocal 如何在“父子” Composition 中传递
CompositionLocal解析流程
setContent 中 @Composable content 外层会添加默认的 CompositionLocalMap ``` original.setContent {
CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
ProvideAndroidCompositionLocals(owner, content)
}
}
@Composable @OptIn(InternalComposeApi::class) fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) { currentComposer.startProviders(values) content() currentComposer.endProviders() } ``` CompositionLocalMap 的解析调用的不是 startGroup() ,而是 startProviders()
``` @InternalComposeApi override fun startProviders(values: Array<out ProvidedValue<*>>) { // 拿到当前 Composition 中的 CompositionLocalMap val parentScope = currentCompositionLocalScope() startGroup(providerKey, provider) startGroup(providerValuesKey, providerValues) // 将 values 解析成 CompositionLocalMap val currentProviders = invokeComposableForResult(this) { compositionLocalMapOf(values, parentScope) } endGroup() val providers: CompositionLocalMap val invalid: Boolean if (inserting) { //合并 parentScope currentProviders providers = updateProviderMapGroup(parentScope, currentProviders) invalid = false //标记当前插入操作的 wirter 已经有了 Provider writerHasAProvider = true } else { /不是新增,判断 currentProviders 的和 SlotTable 中保存的是否一样 //一样就忽略,不一样就更新 }
if (invalid && !inserting) { providerUpdates[reader.currentGroup] = providers } providersInvalidStack.push(providersInvalid.asInt()) providersInvalid = invalid //合并后的 缓存在 providerCache providerCache = providers // groupKey groupObjectKey 不是LayoutNode 合并后的 providers // 将合并后的 providers 保存到 当前 Composition 的 SlotTable 中 start(compositionLocalMapKey, compositionLocalMap, false, providers) }
```
``` private fun currentCompositionLocalScope(group: Int? = null): CompositionLocalMap { if (group == null) //已经有缓存了直接返回 providerCache?.let { return it } // 如果当前是插入操作且标记过 writerHasAProvider // 代码的 if(inserting) 逻辑 if (inserting && writerHasAProvider) { var current = writer.parent while (current > 0) { if (writer.groupKey(current) == compositionLocalMapKey && writer.groupObjectKey(current) == compositionLocalMap ) { @Suppress("UNCHECKED_CAST") val providers = writer.groupAux(current) as CompositionLocalMap //缓存 providers 并返回 providerCache = providers return providers } current = writer.parent(current) } } //没有进行写操作,去 reader 里找 ,找到就缓存 providers 并返回 if (reader.size > 0) { var current = group ?: reader.parent while (current > 0) { if (reader.groupKey(current) == compositionLocalMapKey && reader.groupObjectKey(current) == compositionLocalMap ) { @Suppress("UNCHECKED_CAST") val providers = providerUpdates[current] ?: reader.groupAux(current) as CompositionLocalMap providerCache = providers return providers } current = reader.parent(current) } } //都没有就缓存 parentProvider 并返回 providerCache = parentProvider return parentProvider }
//默认 空 private var parentProvider: CompositionLocalMap = persistentHashMapOf() ``` 举个例子:
CompositionLocal 传递过程
在 buildContext() 时把当前 Composition 中缓存的 ComposerImpl.providerCache 赋值给了CompositionContextImpl.compositionLocalScope。 ``` // ComposerImpl.buildContext() holder.ref.updateCompositionLocalScope(currentCompositionLocalScope())
//CompositionContextImpl fun updateCompositionLocalScope(scope: CompositionLocalMap) { compositionLocalScope = scope } ``` CompositionContextImpl 作为 CompositionContext 作为参数创建 PopupLayout 的 Composition 和 Composer , PopupLayout 的初始组合开启。
invokeComposable() 也就是解析 PopupLayout 的 @Composable content 之前会先调用 startRoot() 方法
private fun doCompose() {
trace("Compose:recompose") {
try {
startRoot() //CompositionLocal 在这里传递
observeDerivedStateRecalculations() {
if (content != null) {
startGroup(invocationKey, invocation)
invokeComposable(this, content)
endGroup()
} else if () {
} else {}
}
endRoot()
} finally {}
}
}
此时的 parentContext 就是 buildContext() 返回的 CompositionContextImpl 对象。
@OptIn(InternalComposeApi::class)
private fun startRoot() {
//CompositionContextImpl.compositionLocalScope
//将 ComposeView 中 Composer 缓存的 providers 赋值给
//当前 PopupLayout 中 Composer 的 parentProvider
parentProvider = parentContext.getCompositionLocalScope()
}
再执行 invokeComposable() 解析 PopupLayout 的 @Composable content 。
同样会先执行 startProviders(),此时虽然 PopupLayout 刚刚开启初始组合但第一次运行 currentCompositionLocalScope() 并不会返回空 Map ,而是传递过来的 providers。
``` //默认 空 //private var parentProvider: CompositionLocalMap = persistentHashMapOf() // ↑ private var parentProvider = parentContext.getCompositionLocalScope()
private fun currentCompositionLocalScope(group: Int? = null): CompositionLocalMap { providerCache = parentProvider return parentProvider } ``` 此时的 startProviders() 会以传递过来的 providers 为基础生成新的 providers 保存到 PopupLayout 的 Composition 的 SlotTable 中,同时缓存到 Composer 中。
在上面的例子中添加 Popup() , CompositionLocal 传递如下图