opoojkk

深入 Compose:从 setContent 到 LayoutNode 绘制原理

lxx
目次

在之前,我一直以为 Compose 是深不可测、难以理解的东西,直到我打开一个 Compose 项目的 setContent 方法,才发现它其实并没有那么复杂。

Compose 和 XML 的不同,只是写 UI 的方式不同而已。而关键差别,就在 setContent 方法上。注意,这里的 setContent 与我们平时在 Activity 中调用的、传入一个 View 或布局 id 的 setContentView 并不是同一个方法。它只是名字相同,实际上完全是另一套实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 {
            // Set content and parent **before** setContentView
            // to have ComposeView create the composition on attach
            setParentCompositionContext(parent)
            setContent(content)
            // Set the view tree owners before setting the content view so that the inflation
            // process and attach listeners will see them already present
            setOwners()
            setContentView(this, DefaultActivityContentLayoutParams)
        }
}

可以看到,ComposeView 继承自 AbstractComposeView,而 AbstractComposeView 又是一个 ViewGroup。当 ComposeView 被添加到 Window 上时,会创建一个 Composition 对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ComposeView.android.kt
override fun onAttachedToWindow() {
    super.onAttachedToWindow()

    previousAttachedWindowToken = windowToken

    if (shouldCreateCompositionOnAttachedToWindow) {
        ensureCompositionCreated()
    }
}

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

ComposeViewsetContent 会创建一个关键的类:AndroidComposeView。可以说它是 Compose 在 Android 上的核心实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Wrapper.android.kt
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)
}

AndroidComposeViewComposeView 的唯一子 View,它把 Compose 和 Android View 系统联系起来。

AndroidComposeView 中,根节点就是 Compose 的 LayoutNode。Compose 中的每个组件,都会由 @Composable 标记,并在编译时由 Kotlin IR 插件自动增加两个参数:Composer $composerint $changedComposer 的唯一实现类 ComposerImpl 会为组件创建 LayoutNode,而 Applier 则负责把 LayoutNode 添加到父节点中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Wrapper.android.kt
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)

    if (owner.coroutineContext != parent.effectCoroutineContext) {
        owner.coroutineContext = parent.effectCoroutineContext
    }

    return wrapped
}

这里,ownerAndroidComposeViewowner.root 是 Compose 的根节点,用于创建 UiApplier


绘制流程 #

Compose 的绘制逻辑也不同于传统 View。传统 View 通常只重写 onDraw 来绘制内容,背景和前景由系统在 draw 中处理。而 Compose 的绘制单元不是 View,而是 LayoutNode,因此绘制操作在 dispatchDraw 中进行分发:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
override fun dispatchDraw(canvas: android.graphics.Canvas) {
    if (!isAttachedToWindow) {
        invalidateLayers(root)
    }
    measureAndLayout()
    Snapshot.sendApplyNotifications()

    isDrawingContent = true
    // 绘制根节点
    canvasHolder.drawInto(canvas) { root.draw(this) }

    if (dirtyLayers.isNotEmpty()) {
        for (i in 0 until dirtyLayers.size) {
            val layer = dirtyLayers[i]
            layer.updateDisplayList()
        }
    }

    if (ViewLayer.shouldUseDispatchDraw) {
        val saveCount = canvas.save()
        canvas.clipRect(0f, 0f, 0f, 0f)
        super.dispatchDraw(canvas)
        canvas.restoreToCount(saveCount)
    }

    dirtyLayers.clear()
    isDrawingContent = false

    if (postponedDirtyLayers != null) {
        val postponed = postponedDirtyLayers!!
        dirtyLayers.addAll(postponed)
        postponed.clear()
    }
}

在这里,root 就是 LayoutNode 的根节点。绘制时,会遍历链表中的节点,并调用节点自身的 draw 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// NodeCoordinator.kt
fun draw(canvas: Canvas, graphicsLayer: GraphicsLayer?) {
    val layer = layer
    if (layer != null) {
        layer.drawLayer(canvas, graphicsLayer)
    } else {
        val x = position.x.toFloat()
        val y = position.y.toFloat()
        canvas.translate(x, y)
        drawContainedDrawModifiers(canvas, graphicsLayer)
        canvas.translate(-x, -y)
    }
}

private fun drawContainedDrawModifiers(canvas: Canvas, graphicsLayer: GraphicsLayer?) {
    val head = head(Nodes.Draw)
    if (head == null) {
        performDraw(canvas, graphicsLayer)
    } else {
        val drawScope = layoutNode.mDrawScope
        drawScope.draw(canvas, size.toSize(), this, head, graphicsLayer)
    }
}

// LayoutNodeDrawScope.kt
internal fun draw(
    canvas: Canvas,
    size: Size,
    coordinator: NodeCoordinator,
    drawNode: Modifier.Node,
    layer: GraphicsLayer?
) {
    drawNode.dispatchForKind(Nodes.Draw) {
        drawDirect(canvas, size, coordinator, it, layer)
    }
}

简单总结:

标签:
Categories: