Compose系列【6】ComposeView

768 阅读7分钟

ComposeView

传统的 Android UI 是通过 View 对象形式来构建的,而 Compose 的 UI 是通过方法的形式构建的。创建一个全新的 Android Compose 项目,它会自动生成 MainActivity :

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ComposeReadTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}
ComponentActivity.setContent

这里有一个 Compose 的入口 setContent { ... } ,这是一个拓展方法:

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView

    if (existingComposeView != null) with(existingComposeView) {
        setParentCompositionContext(parent)
        setContent(content)
    } else ComposeView(this).apply {
        setParentCompositionContext(parent)
        setContent(content)
        setOwners()
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}

decorView 是整个窗口视图树的根节点,在这里,会将 decorView 中的 R.id.content 的 View 强转为 ComposeView,作为根视图,然后从它去设置内容,如果强转失败或其他原因导致 existingComposeView 为 null,则新建一个 ComposeView 对象。

总之第二步来到了 ComposeView 的 setContent。

ComposeView.setContent
class ComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
  	// ...
  	private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
  
		fun setContent(content: @Composable () -> Unit) {
        shouldCreateCompositionOnAttachedToWindow = true
        this.content.value = content
        if (isAttachedToWindow) {
            createComposition()
        }
    }
}

在 setContent 中,有两个重要步骤,一是将方法参数保存到 content 中;第二个步骤是调用 createComposition,即创建组合。

AbstractComposeView.createComposition
    fun createComposition() {
        check(parentContext != null || isAttachedToWindow) {
            "createComposition requires either a parent reference or the View to be attached" +
                "to a window. Attach the View or call setParentCompositionReference."
        }
        ensureCompositionCreated()
    }

    private fun ensureCompositionCreated() {
        if (composition == null) {
            try {
                creatingComposition = true
                composition = setContent(resolveParentCompositionContext()) {
                    Content()
                }
            } finally {
                creatingComposition = false
            }
        }
    }

在 ensureCompositionCreated 里调用 setContent 创建组合(这里的 setContent 是 AbstractComposeView 的拓展函数),并传递一个 上下文和 Content()

上下文是通过 resolveParentCompositionContext() 获取到的,其作用是为当前视图找到合适的 CompositionContext 作为父上下文。这涉及到从父视图、缓存中、或从窗口的 Recomposer(负责管理 Compose 组件的重组操作)中查找正确的上下文。

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

Content() 就是其实现类型 ComposeView 的 setContent 传递进来的内容:

class ComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
  
    @Composable
    override fun Content() {
        content.value?.invoke()
    }
}
AbstractComposeView.setContent

继续分析 AbstractComposeView 的拓展函数 setContent :

internal fun AbstractComposeView.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    GlobalSnapshotManager.ensureStarted()
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews(); null
        } ?: AndroidComposeView(context, parent.effectCoroutineContext).also {
            addView(it.view, DefaultLayoutParams)
        }
    return doSetContent(composeView, parent, content)
}

这里是将 AbstractComposeView 的第一个子节点强转为 AndroidComposeView,如果为空,新建一个 AndroidComposeView,并将其添加到 AbstractComposeView 中。最后执行 doSetContent 函数。

private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    val original = Composition(UiApplier(owner.root), parent)
    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
}

在这个步骤中,创建了一个 原始的 Composition 对象,传递了两个参数:

  • Applier:这里新建了一个 UiApplier 对象,UiApplier 是对视图树结构进行操作的包装器,可以插入/删除/移动节点;
  • CompositionContext:组合上下文。
fun Composition(
    applier: Applier<*>,
    parent: CompositionContext
): Composition =
    CompositionImpl(
        parent,
        applier
    )
WrappedComposition.setContent

然后又创建了一个 WrappedComposition,这里先去尝试获取 tag 强转,失败则新建,然后调用 WrappedComposition 的setContent:

    override fun setContent(content: @Composable () -> Unit) {
        owner.setOnViewTreeOwnersAvailable {
            if (!disposed) {
                val lifecycle = it.lifecycleOwner.lifecycle
                lastContent = content
                if (addedToLifecycle == null) {
                    addedToLifecycle = lifecycle
                    // this will call ON_CREATE synchronously if we already created
                    lifecycle.addObserver(this)
                } else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
                    original.setContent {

                        @Suppress("UNCHECKED_CAST")
                        val inspectionTable =
                            owner.getTag(R.id.inspection_slot_table_set) as?
                                MutableSet<CompositionData>
                                ?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set)
                                    as? MutableSet<CompositionData>
                        if (inspectionTable != null) {
                            inspectionTable.add(currentComposer.compositionData)
                            currentComposer.collectParameterInformation()
                        }

                        LaunchedEffect(owner) { owner.boundsUpdatesEventLoop() }

                        CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
                            ProvideAndroidCompositionLocals(owner, content)
                        }
                    }
                }
            }
        }
    }

在这个方法中,owner 是个 AndroidComposeView,setContent 方法通过与 AndroidComposeView 的生命周期管理集成,确保 Composable 内容只在正确的生命周期状态下被渲染,并且在视图树和生命周期都准备好时,才会执行。

  1. owner.setOnViewTreeOwnersAvailable
    • 这里的 owner 是 AndroidComposeView 的实例,它在视图树可用时执行回调。这通过 setOnViewTreeOwnersAvailable 进行处理,确保 viewTreeOwners 可用时再执行后续逻辑。
    • 一旦 viewTreeOwners 可用,代码中的逻辑才会继续执行。
  2. 检查 disposed 状态
    • 如果 Composition 已经被销毁(disposed 为 true),不会继续执行任何操作,防止内存泄漏和无效操作。
    1. 获取并设置生命周期
    • val lifecycle = it.lifecycleOwner.lifecycle:通过 viewTreeOwners 获取 LifecycleOwner 并从中获取 lifecycle 对象,来跟踪 Compose 组件的生命周期。
    • 将传入的 content 保存为 lastContent,以便在必要时重新渲染。
    1. 添加生命周期观察者
    • 如果该 Composition 尚未添加到生命周期中(addedToLifecycle == null),则将 lifecycle 添加为观察者,并同步调用 ON_CREATE,即立即触发生命周期事件。这确保了 Composition 可以在 ON_CREATE 之后正确初始化。
    • 如果 lifecycle 的状态已经是 CREATED 或更高的状态,说明组件已经被创建了,接下来会设置 original.setContent 来渲染实际内容。
    1. 设置实际内容 (original.setContent)
    • 使用原始 Composition 的 setContent 来设置实际的 Composable 内容。
    • 其中使用了 @Suppress("UNCHECKED_CAST") 来从视图的 tag 中检索 inspectionTable(用于调试或检查 Compose 的内部状态的结构)。
    • inspectionTable 用来跟踪 Composition 的数据和参数信息,以支持 Compose 的调试和检查。
    • 将当前 Composer 的数据(currentComposer.compositionData)添加到 inspectionTable 中,并收集参数信息。
    1. LaunchedEffect 和 CompositionLocalProvider
    • 使用 LaunchedEffect 来启动 owner.boundsUpdatesEventLoop(),它可能是一个监听视图边界变化的循环任务,用于响应视图的动态调整。
    • 通过 CompositionLocalProvider 提供 LocalInspectionTables(当前组合的调试信息表)和 AndroidCompositionLocals(将 Android 上下文、生命周期等本地化信息传递给 Compose 组件)给 Composable 内容。确保内容可以正确使用本地化的上下文信息。
CompositionImpl.setContent
    override fun setContent(content: @Composable () -> Unit) {
        composeInitial(content)
    }

		private fun composeInitial(content: @Composable () -> Unit) {
        check(!disposed) { "The composition is disposed" }
        this.composable = content
        parent.composeInitial(this, composable)
    }

parent 这里是 CompositionContext:

CompositionContext.composeInitial
    internal abstract fun composeInitial(
        composition: ControlledComposition,
        content: @Composable () -> Unit
    )

其实现是 Recomposer。

Recomposer.composeInitial
    internal override fun composeInitial(
        composition: ControlledComposition,
        content: @Composable () -> Unit
    ) {
        val composerWasComposing = composition.isComposing
        try {
            composing(composition, null) {
                composition.composeContent(content)
            }
        } catch (e: Exception) {
            processCompositionError(e, composition, recoverable = true)
            return
        }

        // TODO(b/143755743)
        if (!composerWasComposing) {
            Snapshot.notifyObjectsInitialized()
        }

        synchronized(stateLock) {
            if (_state.value > State.ShuttingDown) {
                if (composition !in knownCompositions) {
                    addKnownCompositionLocked(composition)
                }
            }
        }

        try {
            performInitialMovableContentInserts(composition)
        } catch (e: Exception) {
            processCompositionError(e, composition, recoverable = true)
            return
        }

        try {
            composition.applyChanges()
            composition.applyLateChanges()
        } catch (e: Exception) {
            processCompositionError(e)
            return
        }

        if (!composerWasComposing) {
            // Ensure that any state objects created during applyChanges are seen as changed
            // if modified after this call.
            Snapshot.notifyObjectsInitialized()
        }
    }
  1. **composerWasComposing 变量:**首先记录 composition.isComposing,判断当前 composition 是否正在处于组合过程中。这个标志用于在后续逻辑中判断是否需要进行某些后处理。

  2. composing 函数:

    1. composing(composition, null) { ... } 是一个关键的组合函数,它确保 composeContent 调用时在一个安全的组合上下文中执行。
    2. composition.composeContent(content) 实际执行了传入的 Composable 内容的组合操作,将 UI 树生成和渲染逻辑交给 Compose 运行时进行处理。
  3. 异常处理:

    1. 在组合过程中使用 try-catch 块来捕获异常。异常被捕获后,通过 processCompositionError 进行处理。
    2. 如果发生了组合错误(如某个 Composable 抛出了异常),该异常会被处理为可恢复的错误(recoverable = true),并且函数会提前返回,避免继续执行后续的操作。
  4. Snapshot.notifyObjectsInitialized():

    1. 在完成组合后,调用 Snapshot.notifyObjectsInitialized() 通知 Compose 框架,任何在组合过程中创建的状态对象现在已经被初始化。这是 Jetpack Compose 的快照系统的一部分,用于跟踪和管理 UI 状态。
  5. 同步状态检查:

    1. 使用 synchronized(stateLock) 同步块对状态进行线程安全的检查和更新,确保在多线程环境下不会出现并发问题。
    2. 如果当前 composition 的状态超过了 State.ShuttingDown,并且 composition 尚未在已知组合中,则将其添加到 knownCompositions 集合中。这意味着该组合处于有效状态,并且需要被 Compose 进行管理。
  6. 插入可移动内容 (performInitialMovableContentInserts):

    1. 调用 performInitialMovableContentInserts(composition) 以处理任何需要移动的内容插入。这个函数处理的是组合中需要动态插入或移动的部分,例如一些动态布局的组件。
    2. 如果在这个过程中发生异常,同样使用 processCompositionError 进行处理。
  7. 应用更改 (applyChanges 和 applyLateChanges):

    1. composition.applyChanges():应用在组合过程中计算出来的变更,更新 UI 树。
    2. composition.applyLateChanges():应用后续需要处理的变更,确保所有状态更新和 UI 改变都正确反映在屏幕上。
    3. 如果在这些过程中抛出异常,则捕获并处理,确保组合不会崩溃。
  8. 再次调用 Snapshot.notifyObjectsInitialized():

    如果 composerWasComposing 为 false(表示这是一次新的组合),再次调用 Snapshot.notifyObjectsInitialized()。这样可以确保在应用完组合和状态更改后,任何在 applyChanges 期间创建的状态对象在后续修改时都会被检测到。

ComposeView.png