19.5 Compose CompositionContext 和 再谈 CompositionLocal

语言: CN / TW / HK

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() 就结束了。

这里我们再深入了解

  1. CompositionLocal 如何解析
  2. 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() ``` 举个例子:

BA07570B-C87C-4157-9F4D-285202C7B802.png

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 传递如下图

FDE7B10A-A66C-41F7-A1EF-865B14BA138E.png