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