19.1 Compose 初始组合流程分析

689 阅读5分钟

前面两节中 Snackbar 和 NavigationDrawer 这两个组件都是通过在原来的 Compose 树中添加/删除来实现的,组件本身会解析成 LayoutNode 添加到 AndroidComposeView 的 root 根节点中。

Compose 中还有两个弹窗效果的组件,Popup 和 Dialog。 他们两个都是基于 AbstractComposeView 将 @Composable content 在另一个 Compose 树中解析显示的,这章我们先来好好了解 AbstractComposeView ,下一章再来研究 Popup 和 Dialog。

AbstractComposeView 作用是在原生 View 中使用 Jetpack Compose UI 的 View 基类。继承 ViewGroup ,其实现子类是原生 View 体系中 Jetpack Compose UI 的持有者,是原生 View 体系和 Compose 之间的桥梁。

其实现子类是原生 View 体系中 Jetpack Compose UI 的持有者的意思是:

  • 原生 View 体系中  addView(view :AbstractComposeView)

  • AbstractComposeView 通过 setContent 方法来解析 @Composable content

AbstractComposeView 在 Compose 中有三个实现类,ComposeView 、PopupLayout 和 DialogLayout。

ComposeView 应用在 ComponentActivity#setContent ,后两个分别用来实现 Popup 和 Dialog。

通过 Compose 初始组合流程来了解 AbstractComposeView 是如何将原生 View 与 Compose UI 联系在一起的,以及整个 Compose 环境是如何一步步完善的。

ComponentActivity#setContent

每一次我们新建 Compose 工程,IDE 都会自动在 MainActivity#onCreate 中自动加上下面的代码,然后我们就开始开心的往 setContent 中添加 @Compose fun 的代码了。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      	// ComponentActivity.setContent
        setContent { 
          //实现 Compose UI
        }
    }
}

ComponentActivity#setContent  源码也很简单

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
  	//val existingComposeView = null
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView
	
    if (existingComposeView != null) with(existingComposeView) {
      //existingComposeView = null 不进 if 分支  
      setParentCompositionContext(parent)
        setContent(content)
    } else ComposeView(this).apply { //1 创建 ComposeView 对象
        setParentCompositionContext(parent) //parent = null
        setContent(content)//2 将 @Composable content 保存到 ComposeView 中
        setOwners() 
      	//3 setContentView 不在使用 xml 而是添加 ComposeView 对象
      	//将 ComposeView 作为 ContentView 添加到 Activity 的 Windwow.decorView中
        setContentView(this, DefaultActivityContentLayoutParams) 
    }
}

/**
这个方法单纯就是为了兼容 AppCompat 1.3 以前的版本。确保 Compose 中使用 1,2,3 处时不会报错
 */
private fun ComponentActivity.setOwners() {
    val decorView = window.decorView
  	//Lifecycle 生命周期感知能力
    if (ViewTreeLifecycleOwner.get(decorView) == null) {
        ViewTreeLifecycleOwner.set(decorView, this)
    }
  	//从 ViewModelStore 缓存/获取 ViewModel
    if (ViewTreeViewModelStoreOwner.get(decorView) == null) {
        ViewTreeViewModelStoreOwner.set(decorView, this)
    }
  	//使用 SavedStateRegistry 来缓存/获取 Bundle 类型的状态数据
    if (ViewTreeSavedStateRegistryOwner.get(decorView) == null) {
        ViewTreeSavedStateRegistryOwner.set(decorView, this)
    }
}

//Compose View 默认是 WRAP_CONTENT 
private val DefaultActivityContentLayoutParams = ViewGroup.LayoutParams(
    ViewGroup.LayoutParams.WRAP_CONTENT,
    ViewGroup.LayoutParams.WRAP_CONTENT
)

方法的作用:

创建 ComposeView 对象,将 @Composable content 的 Compose UI 赋值给 ComposeView 的 content 属性,然后将其作为 ContentView 设置到 Activity 中。

再来看一下 ComposeView 源码

/**
   一个可以持有 Jetpack Compose UI content 的 Android 原生 View
 * A [android.view.View] that can host Jetpack Compose UI content.
 	使用 setContent 方法为 View 设置 composable content
 * Use [setContent] to supply the content composable function for the view.
 */
class ComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {

    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)

    @Suppress("RedundantVisibilityModifier")
    protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
        private set

    @Composable
    override fun Content() {
        content.value?.invoke()
    }

    override fun getAccessibilityClassName(): CharSequence {
        return javaClass.name
    }

    fun setContent(content: @Composable () -> Unit) {
        shouldCreateCompositionOnAttachedToWindow = true
        this.content.value = content
        if (isAttachedToWindow) {
            createComposition()
        }
    }
}

ComposeView 的 content 属性的类型是 MutableState<@Composable () -> Unit> ,ComposeView#Content() 方法调用时才会真正执行 @Composable 函数。

ComposeView#setContent() 方法:

  1. ComposeView 在 onAttachedToWindow 时创建组合 Composition,

  2. 将 @Composable content 函数赋值给 MutableState 类型的属性 content,

  3. 判断 ComposeView 是否已经  AttachedToWindow ,来决定是否创建组合 Composition。

经过 ComponentActivity#setContent() 后 Activity UI 结构如下

1AD85D55-1F0C-4A93-922A-0CA68306BBDB.png

ComponentActivity#setContent() 发生在 Activity#onCreate() ,此时并不会执行 createComposition()。

当 Activity 开始测绘流程时 ViewRootImp 会调用 DecorView 的 dispatchAttachedToWindow(),原生 View 树中所有的 View 都会触发 onAttachedToWindow() 回调。

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()

        previousAttachedWindowToken = windowToken
		//ComposeView#setContent() 时设置为 true
        if (shouldCreateCompositionOnAttachedToWindow) {
            ensureCompositionCreated()
        }
    }

接着 ensureCompositionCreated() 开启初始组合流程

ComposeView#ensureCompositionCreated

@Suppress("DEPRECATION") // Still using ViewGroup.setContent for now
private fun ensureCompositionCreated() {
	if (composition == null) { //composition = null
		try {
			creatingComposition = true
          	//	resolveParentCompositionContext = Recomposer		  
			composition = setContent(resolveParentCompositionContext()) {
				Content()
            }
        } finally {
          creatingComposition = false
        }
    }
}

方法中先判断 composition 是否已经存在,存在就代表已经初始组合过了,不需要再进行组合流程,更新 UI  走重组流程。这就是我们在第一章 Compose 基础概念中说的在 Composeable 的生命周期中会进行 1 次初始组合和 0 到 n 次重组的原因。

384686FA-BDC3-4882-9C2E-C70DAEE8A804.png

刚刚触发 onAttachedToWindow() 回调,显然 composition 依旧是 null ,接下来会执行两个方法调用。

ComposeView#resolveParentCompositionContext

private fun resolveParentCompositionContext() = parentContext//null
	?: findViewTreeCompositionContext()?.cacheIfAlive()//null
	?: cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive }//null
	?: windowRecomposer.cacheIfAlive() //Recomposer

parentContext 在 ComponentActivity#setContent 时并没有传值,使用的是默认值 null 。

findViewTreeCompositionContext() 和 cachedViewTreeCompositionContext 取的都是创建 parentContext 时的缓存值,此时还没有创建过所以并不存在缓存。创建 parentContext 就只能交给最后一行代码来负责了。

windowRecomposer.cacheIfAlive() 要分成两步来看:

  1. ComposeView.getWindowRecomposer() : 创建 Recomposer 对象
  2. Recomposer.cacheIfAlive(): 将 Recomposer 对象的弱引用保存到 ComposeView 的 cachedViewTreeCompositionContext 属性中

20A1C2DF-56BE-4B98-8C79-8AEB00211191.png

  • Recomposer 是 CompositionContext 的实现类
  • ComposeView 的 parentContext 是 Recomposer  对象
  • Recomposer 对象在创建时会保存到 Activity ContentView 中,所以默认情况下一个 Activity 中所有的 ComposeView 都使用同一个  Recomposer  对象作为 parentContext

有了 CompositionContext 这个参数后,就开始调用 AbstractComposeView 拓展 setContent() 方法开始初始组合。

AbstractComposeView#setContent

internal fun AbstractComposeView.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
  	//GlobalSnapshotManager 还没深入了解
    GlobalSnapshotManager.ensureStarted()
  	//创建 composeView 
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews(); null
        } ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
    // 进行初始组合并返回 composition    
    return doSetContent(composeView, parent, content)
}

这是一个拓展方法,在方法中只是生成 AndroidComposeView 类型的 composeView ,并将其添加到 ComposeView 中。

AndroidComposeView 我们都不陌生了前面分析事件传递、测绘的时候都有提到过。所有的 Compose 组件都会解析成 LayoutNode 保存到  AndroidComposeView root 的根节点中。

DD867CBB-583D-4695-9465-287B3118EEB6.png

doSetContent

private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    if (inspectionWanted(owner)) {
        owner.setTag(
            R.id.inspection_slot_table_set,
            Collections.newSetFromMap(WeakHashMap<CompositionData, Boolean>())
        )
        enableDebugInspectorInfo()
    }
  	//创建 composition ,注意 这里还创建了 UiApplier 对象
    val original = Composition(UiApplier(owner.root), parent)
  	//为 composition 添加生命周期感知能力
    val wrapped = owner.view.getTag(R.id.wrapped_composition_tag)
        as? WrappedComposition
        ?: WrappedComposition(owner, original).also {
            owner.view.setTag(R.id.wrapped_composition_tag, it)
        }
	//开启初始组合
    wrapped.setContent(content)
    return wrapped
}
  1. 创建 UiApplier 对象
  2. 创建 CompositionImpl 对象
  3. 为 CompositionImpl 对象包装生命周期感知能力,并缓存到 AndroidComposeView 中
  4. 开启初始组合
  5. 将包装后的 CompositionImpl 对象,返回给 ComposeView#ensureCompositionCreated 方法

wrapped.setContent() 调用流程如下:

image.png

总结

从 ComponentActivity#setContent 创建 ComposeView 到 AbstractComposeView#setContent 初始组合结束,这个过程中 ComposeView 中的属性一步步被赋值,Activity UI 也一步步实例化,Compose 运行环境也一步步完善。

406177C5-A75C-47A6-A066-1921228C4EE5.png

过程中涉及到了下面几个重要的类型,抛开与 View 、Compose 函数解析相关的类型 ,CompositionContext(Compose 运行环境)和 Composition (Compose 组合信息)在整个 Compose 中都有着重要的作用。我们后面继续分析

image.png