• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

19.5 Compose CompositionContext 和 再谈 CompositionLocal

武飞扬头像
给大佬们点赞
帮助123

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(this@ComposerImpl.composition)
            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()

举个例子:

学新通技术网

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

学新通技术网

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanffhfk
系列文章
更多 icon
同类精品
更多 icon
继续加载