深入 Compose:从 setContent 到 LayoutNode 绘制原理 lxx 2025年10月06日 目次 在之前,我一直以为 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
}
}
}
ComposeView
的 setContent
会创建一个关键的类: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 )
}
AndroidComposeView
是 ComposeView
的唯一子 View,它把 Compose 和 Android View 系统联系起来。
在 AndroidComposeView
中,根节点就是 Compose 的 LayoutNode
。Compose 中的每个组件,都会由 @Composable
标记,并在编译时由 Kotlin IR 插件自动增加两个参数:Composer $composer
和 int $changed
。Composer
的唯一实现类 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
}
这里,owner
是 AndroidComposeView
,owner.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 ( !is AttachedToWindow ) {
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 )
}
}
简单总结:
ComposeView
→ AndroidComposeView
→ LayoutNode
@Composable
函数组件 → ComposerImpl
→ LayoutNode
→ UiApplier
绘制由 dispatchDraw
触发,通过 Canvas
绘制 LayoutNode
的内容 Compose 的 UI 结构和 View 层级不同,它直接操作 LayoutNode
树,而不是 View 树 上篇:Paging3核心解析:Kotlin Flow 如何实现内存与数据双高效
下篇:Jetpack DataStore:Android应用中的现代数据存储方法