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(this@ComposerImpl.composition)
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 传递如下图
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanffhfk
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24