Jetpack Compose setContent 源碼分析

Jetpack Compose setContent 源碼分析

從何入手

先來了解一下Compose架構(gòu)的分層設(shè)計(jì)

由上至下 說明 運(yùn)用
material 提供了Material Design一套風(fēng)格體系,包含主題系統(tǒng)糙及、樣式化組件 Button藤违、AlertDialog等等
foundation 相當(dāng)于面向開發(fā)者的跟基層春叫,包含完整的UI系統(tǒng)和實(shí)用布局 LazyList灿意、Row攒砖、Column等等
animation 動(dòng)畫層作郭,包含平移鲸匿、漸變、縮放等等镊绪,并且提供了方便開發(fā)者的動(dòng)畫組件 animate**AsState匀伏、Transition、Animatable蝴韭、AnimatedVisibility等等
ui ui相關(guān)的基礎(chǔ)功能够颠,包括自定義布局、繪制榄鉴、觸摸反饋等等 ComposeView摧找、Layout核行、LayoutNode等等
runtime 最底層的概念模型牢硅,包括數(shù)據(jù)結(jié)構(gòu)蹬耘、狀態(tài)處理、數(shù)據(jù)同步等等 mutableStateOf减余、remember等等
compiler 基于Kotlin的編譯器插件 處理@Composable函數(shù)

了解完整體的分層設(shè)計(jì)综苔,而要分析的setContent()源碼是處于ui層和runtime層。

Compose版本號(hào)采用1.0.1

代碼塊頂部注釋為類位置

先看一段Compose代碼

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 入口函數(shù)
        setContent { // content 函數(shù)對(duì)象
            Text("Hello Compose")
        }
    }
}

寫法很簡(jiǎn)單位岔,所以我們的目的也很簡(jiǎn)單如筛,就是看看setContent()函數(shù)講Text("Hello Compose")進(jìn)行怎么樣的邏輯傳遞。為了之后的代碼跟隨能夠更加的清晰抒抬,后續(xù)講到入口函數(shù)對(duì)象就等同于Text("Hello Compose")

ComponentActivity.setContent()

// androidx.activity.compose.ComponentActivity
public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
    // 1. 通過decorView找到ContentView杨刨,再獲得第一個(gè)ComposeView
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView

    // 2. 如果ComposeView為空則走初始化流程
    if (existingComposeView != null) with(existingComposeView) {
        setParentCompositionContext(parent)
        setContent(content)
    } else ComposeView(this).apply {
        // 3. 初始化ComposeView肯定為空,則進(jìn)入這邊
        setParentCompositionContext(parent)
        // 4. 把入口函數(shù)對(duì)象傳入ComposeView
        setContent(content)
        setOwners()
        // 5. 把ComposeView設(shè)置進(jìn)ContentView
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}

從第一步可以了解到擦剑,原來在當(dāng)前窗口的decorView中的android.R.id.content中第0個(gè)位置妖胀,會(huì)存在一個(gè)ComposeView,所以ComposeView結(jié)構(gòu)圖可以理解成:

ComposeView 結(jié)構(gòu)圖.png

難道這個(gè)ComposeView肯定跟傳統(tǒng)的View/ViewGroup有關(guān)系嗎伶唯?遇事不決就看看Compose的繼承關(guān)系呐舔。


image.png

果真如此掷匠,也能驗(yàn)證Compose架構(gòu)的分層設(shè)計(jì)中上下關(guān)系,animation涂臣、foundation的工作也是在ComposeView中進(jìn)行,另外還能延伸出一個(gè)問題售担,那是不是Compose和傳統(tǒng)View/ViewGroup能夠互相交互并且能在同一個(gè)activity混合開發(fā)赁遗?這個(gè)目前沒有研究過,就先略過了族铆。岩四。。

第二步的話骑素,由于分析就是初始化狀態(tài)的流程且existingComposeView肯定為空炫乓,所以直接進(jìn)入到第三步,傳遞了parentsetParentCompositionContext方法献丑,按照案例的入口函數(shù)末捣,

// 入口函數(shù)
setContent(parent = null) { // content 函數(shù)對(duì)象
    Text("Hello Compose")
}

所以當(dāng)前parent為空,再看看方法里具體做了哪些创橄。

// androidx.compose.ui.platform.AbstractComposeView
fun setParentCompositionContext(parent: CompositionContext?) {
    parentContext = parent
}

只是賦值操作箩做,目前parentContext為空

第四步發(fā)現(xiàn)又調(diào)用了一個(gè)相同方法名的setContent,在之后的分析還會(huì)出現(xiàn)一個(gè)類似的setContent妥畏,每一個(gè)方法作用都是不一樣邦邦,那看看當(dāng)前ComposeView.setContent(入口函數(shù)對(duì)象)做了什么事情

// androidx.compose.ui.platform.ComposeView
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()
    }

    fun setContent(content: @Composable () -> Unit) {
        // 翻譯為 “應(yīng)該在附加到窗口上創(chuàng)建合成”安吁,做一個(gè)標(biāo)記
        shouldCreateCompositionOnAttachedToWindow = true
        // 賦值操作
        this.content.value = content
        if (isAttachedToWindow) {
            // 暫時(shí)不會(huì)進(jìn)入
            createComposition()
        }
    }
}

整個(gè)ComposeView的類結(jié)構(gòu)

AbstractComposeView
    abstract fun Content()
    open val shouldCreateCompositionOnAttachedToWindow: Boolean
        ComposeView
            val content = mutableStateOf
            fun setContent()

這樣一看理解ComposeView也很容易了,它只是做了一個(gè)預(yù)備動(dòng)作燃辖,告訴AbstractComposeView有人(調(diào)用ComposeView.setContent()后)把預(yù)備狀態(tài)修改成準(zhǔn)備就緒啦(shouldCreateCompositionOnAttachedToWindow = true)鬼店,并且入口函數(shù)對(duì)象也存儲(chǔ)好了,等你來拿走處理了(Content() = 入口函數(shù)對(duì)象)黔龟。

所以前兩句就是給AbstractComposeView修改和準(zhǔn)備需要的值妇智。

if對(duì)于初始化分析來說,根本不會(huì)進(jìn)入氏身,因?yàn)楫?dāng)前的ComponentActivity.setContent巍棱,還沒用執(zhí)行到第五步setContentView,所以isAttachedToWindow肯定為false蛋欣。

那再看第五步航徙,setContentView再熟悉不過了,添加到android.R.id.content中陷虎。之后流程居然就沒了到踏?第四步都準(zhǔn)備就緒了,之后就沒有啟動(dòng)邏輯了泻红?

回想一下之前講的ComposeView的繼承關(guān)系夭禽,是View的子類,那setContentView后ComposeView被附加到Window上后谊路,會(huì)回調(diào)方法onAttachedToWindow()讹躯。我們知道ComposeView的沒有這個(gè)方法的缠劝,在父類AbstractComposeView中找到了該實(shí)現(xiàn)潮梯。

AbstractComposeView.onAttachedToWindow()

// androidx.compose.ui.platform.AbstractComposeView
override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    previousAttachedWindowToken = windowToken
    // ComposeView 修改后預(yù)備狀態(tài)
    if (shouldCreateCompositionOnAttachedToWindow) {
        // 真正啟動(dòng)的地方
        ensureCompositionCreated()
    }
}

shouldCreateCompositionOnAttachedToWindow這個(gè)值Compose.setContent()已經(jīng)修改為true,所以直接看ensureCompositionCreated()

// androidx.compose.ui.platform.AbstractComposeView
private fun ensureCompositionCreated() {
    // composition 初始化流程為空
    if (composition == null) {
        try {
            creatingComposition = true
            // 又來一個(gè) setContent惨恭?
            composition = setContent(resolveParentCompositionContext()) {
                // 抽象方法
                Content()
            }
        } finally {
            creatingComposition = false
        }
    }
}

composition還不確定是做什么的秉馏,先一步一步解析這個(gè)setContent涉及的內(nèi)容,看看方法的入?yún)ⅰ?/p>

// androidx.compose.ui.platform.Wrapper_androidKt.class
internal fun ViewGroup.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
...

parent以目前的知識(shí)點(diǎn)也無法對(duì)它進(jìn)行分析脱羡,而content就是我們的入口函數(shù)對(duì)象萝究。調(diào)用的Content()抽象方法,方法的實(shí)現(xiàn)之前講過的锉罐,內(nèi)部返回的就是ComposeView.content.value

為了了解parent為何物帆竹,就要看看resolveParentCompositionContext()方法。

resolveParentCompositionContext()

// androidx.compose.ui.platform.AbstractComposeView
private fun resolveParentCompositionContext() = parentContext
    ?: findViewTreeCompositionContext()?.also { cachedViewTreeCompositionContext = it }
    ?: cachedViewTreeCompositionContext
    ?: windowRecomposer.also { cachedViewTreeCompositionContext = it }

一大堆判空邏輯脓规,那就挨個(gè)分析栽连。

parentContext: 就是ComponentActivity.setContent()中第三步流程,并且還知道當(dāng)前案例傳遞為空。

findViewTreeCompositionContext(): 再看看源碼怎么尋找

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
fun View.findViewTreeCompositionContext(): CompositionContext? {
    var found: CompositionContext? = compositionContext
    if (found != null) return found
    var parent: ViewParent? = parent
    while (found == null && parent is View) {
        found = parent.compositionContext
        parent = parent.getParent()
    }
    return found
}

初始化流程默認(rèn)compositionContext為空秒紧,所以找上一層 parentVew绢陌, 然而整個(gè)while也是找不到需要的compositionContext,所以findViewTreeCompositionContext也會(huì)返回null熔恢。

cachedViewTreeCompositionContext: 是跟隨上一個(gè)findViewTreeCompositionContext()邏輯走的脐湾,上一個(gè)為空則cachedViewTreeCompositionContext也會(huì)返回空。

windowRecomposer: 看看源碼怎么尋找

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
internal val View.windowRecomposer: Recomposer
    get() {
        check(isAttachedToWindow) {
            "Cannot locate windowRecomposer; View $this is not attached to a window"
        }
        // 擴(kuò)展函數(shù)拿到 contentChild
        val rootView = contentChild
        return when (val rootParentRef = rootView.compositionContext) {
            // 初始化流程為null
            null -> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)
            is Recomposer -> rootParentRef
            else -> error("root viewTreeParentCompositionContext is not a Recomposer")
        }
    }

private val View.contentChild: View
    get() {
        var self: View = this
        var parent: ViewParent? = self.parent
        while (parent is View) {
            // 拿到 ComposeView 后返回
            if (parent.id == android.R.id.content) return self
            self = parent
            parent = self.parent
        }
        return self
    }

contentChild: View的擴(kuò)展屬性绩聘,當(dāng)前view的parentId等于android.R.id.content時(shí)沥割,會(huì)返回當(dāng)前view。根據(jù)之前的知識(shí)(ComposeView結(jié)構(gòu)圖)就可以得知凿菩,當(dāng)前view肯定是初始化setContentView傳入的ComposeView。

再根據(jù)當(dāng)前為初始化流程帜讲,所以rootView.compositionContext肯定也是為空衅谷,會(huì)進(jìn)入WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)∷平看到這邊整個(gè)流程都是在尋找compositionContext获黔,但是第一次進(jìn)入頁(yè)面發(fā)現(xiàn)各種方法加擴(kuò)展屬性尋找,都?jí)焊也坏皆谘椤姆椒?code>create***玷氏,其實(shí)就可以知道找不到我就去給你創(chuàng)建一個(gè),并且這個(gè)方法還把rootView(ComposeView)傳入腋舌,在還沒有看createAndInstallWindowRecomposer(rootView)盏触,其實(shí)也可以猜出來,創(chuàng)建compositionContext是肯定的块饺,并且還會(huì)把創(chuàng)建的compositionContext存儲(chǔ)到rootView(ComposeView)其中赞辩,之后再次尋找就有緩存可尋。

WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)

刪減了部分代碼

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
fun interface WindowRecomposerFactory {
    fun createRecomposer(windowRootView: View): Recomposer

    companion object {
        val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView ->
            // 4. createRecomposer() lambda調(diào)用createLifecycleAwareViewTreeRecomposer()
            rootView.createLifecycleAwareViewTreeRecomposer()
        }
    }
}

private fun View.createLifecycleAwareViewTreeRecomposer(): Recomposer {
    ... 
    // 5. 創(chuàng)建 Recomposer
    val recomposer = Recomposer(contextWithClock)
    ...
    // 6. 返回 Recomposer
    return recomposer
}

object WindowRecomposerPolicy {

    private val factory = AtomicReference<WindowRecomposerFactory>(
        // 2. 創(chuàng)建 WindowRecomposerFactory
        WindowRecomposerFactory.LifecycleAware
    )

    ...

    // rootView = ComposeView
    internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {
        // 1. factory.get() 創(chuàng)建 WindowRecomposerFactory
        // 3. createRecomposer(rootView) 調(diào)用 WindowRecomposerFactory.createRecomposer()
        val newRecomposer = factory.get().createRecomposer(rootView)
        // 7. compositionContext 賦值到 ComposeView Tag數(shù)組中
        rootView.compositionContext = newRecomposer
                ...
        // 8. 整個(gè)創(chuàng)建 compositionContext 結(jié)束
        return newRecomposer
    }
}

第一步到第三步是用工廠模式WindowRecomposerFactory來創(chuàng)建Recomposer授艰”嫠裕回想一下我們不是要拿CompositionContext對(duì)象的么?那這個(gè)Recomposer是何方神圣淮腾?

看一下繼承關(guān)系

對(duì)于Recomposer怎么理解糟需,官方注解如下

/**
 * The scheduler for performing recomposition and applying updates to one or more [Composition]s.
 * 用于執(zhí)行重組并將更新應(yīng)用到一個(gè)或多個(gè) [Composition] 的調(diào)度程序。
 * 先了解定義谷朝,之后會(huì)分析Recomposer的內(nèi)部運(yùn)用
 */

所以第四步到第六步就是創(chuàng)建CompositionContext洲押,并且返回賦值給對(duì)象newRecomposer。

第七步是newRecomposer賦值給rootView.compositionContext徘禁, 看看賦值過程诅诱。

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
var View.compositionContext: CompositionContext?
    get() = getTag(R.id.androidx_compose_ui_view_composition_context) as? CompositionContext
    set(value) {
        setTag(R.id.androidx_compose_ui_view_composition_context, value)
    }

就是把CompositionContext添加到ComposeView的Tag數(shù)組,在Compose ui層的體系中送朱,發(fā)現(xiàn)有很多類似的寫法娘荡,通過set/get Tag來存取值干旁,且之后要分析的流程也有類似寫法。

第八步整個(gè)獲取CompositionContext流程終于結(jié)束了炮沐。

ViewGroup.setContent()

這個(gè)時(shí)候就要再貼一下之前的流程分析争群。

// androidx.compose.ui.platform.AbstractComposeView
private fun ensureCompositionCreated() {
    // composition 初始化流程為空
    if (composition == null) {
        try {
            creatingComposition = true
            // 又來一個(gè) setContent
            composition = setContent(resolveParentCompositionContext()) {
                // 抽象方法
                Content()
            }
        } finally {
            creatingComposition = false
        }
    }
}

// androidx.compose.ui.platform.Wrapper_androidKt.class
internal fun ViewGroup.setContent(
    parent: CompositionContext, // Recomposer
    content: @Composable () -> Unit // 入口函數(shù)對(duì)象
): Composition {
    GlobalSnapshotManager.ensureStarted()
    // 獲得 AndroidComposeView
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews(); null
        } ?: AndroidComposeView(context).also { 
          // 添加到 ViewGroup(ComposeView)
          addView(it.view, DefaultLayoutParams) 
        }
    // 又來一個(gè)doSetContent
    return doSetContent(composeView, parent, content)
}

ViewGroup.setContent方法很簡(jiǎn)單,獲得AndroidComposeView添加到ComposeView大年,然后再調(diào)用doSetContent()换薄。

以初始化流程來看肯定會(huì)創(chuàng)建AndroidComposeView,AndroidComposeView的結(jié)構(gòu)圖就可以理解為:

AndroidComposeView 結(jié)構(gòu)圖.png
// // androidx.compose.ui.platform.Wrapper_androidKt.class
private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext, // Recomposer 
    content: @Composable () -> Unit // 入口函數(shù)對(duì)象
): Composition {
    ...
    // 創(chuàng)建 Composition
    val original = Composition(UiApplier(owner.root), parent)
    // 常見 WrappedComposition 并賦值到 AndroidComposeView 的Tag組數(shù)
    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)
        }
    // 又來一個(gè)setContent
    wrapped.setContent(content)
    return wrapped
}

Composition(UiApplier(owner.root), parent): 之前分析了parent翔试,就是通過工廠模式創(chuàng)建的Recomposer轻要,那要理解Composition還需要看看UiApplier是一個(gè)什么類?

UiApplier

internal class UiApplier(
    root: LayoutNode
)

參數(shù)需要一個(gè)LayoutNode垦缅,那這個(gè)LayoutNode又是什么了冲泥?

先從目前的流程場(chǎng)景分析的的話,就直接查看傳遞的AndroidComposeView.root

// androidx.compose.ui.platform.AndroidComposeView
override val root = LayoutNode().also {
    it.measurePolicy = RootMeasurePolicy
    it.modifier = Modifier
        .then(semanticsModifier)
        .then(_focusManager.modifier)
        .then(keyInputModifier)
}

原來AndroidComposeView有這個(gè)屬性對(duì)象壁涎,布局層次結(jié)構(gòu)中的一個(gè)元素凡恍,用于compose UI構(gòu)建,那又有一個(gè)問題了怔球,當(dāng)前是確定AndroidComposeView有這個(gè)元素嚼酝,那入口函數(shù)對(duì)象也是用于compose UI構(gòu)建,那它有沒有了竟坛?

答案是肯定有的闽巩,查看一下Text()最底層實(shí)現(xiàn)源碼

// androidx.compose.foundation.text.CoreTextKt.class
internal fun CoreText(
    ...
) {
    ...
    Layout(
        content = if (inlineComposables.isEmpty()) {
            {}
        } else {
            { InlineChildren(text, inlineComposables) }
        },
        modifier = modifier
            .then(controller.modifiers)
            ...
                ,
        measurePolicy = controller.measurePolicy
    )
        ...
}

// androidx.compose.ui.layout.LayoutKt.class
@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
        },
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}

最終是調(diào)用了ReusableComposeNode<ComposeUiNode, Applier<Any>>,而ComposeUiNode就是LayoutNode的實(shí)現(xiàn)接口流码。

internal class LayoutNode : Measurable, Remeasurement, OwnerScope, LayoutInfo, ComposeUiNode

所以結(jié)合繼承關(guān)系可以把LayoutNode結(jié)構(gòu)理解成:

LayoutNode 結(jié)構(gòu)圖.png

對(duì)于UiApplier又官,就可以小結(jié)一下

UiApplier是一個(gè)視圖工具類,讓其他使用者能更方便的操作root: LayoutNode漫试。內(nèi)部維護(hù)了一個(gè)stack = mutableListOf<T>()代表類似上圖的視圖樹六敬,當(dāng)視圖樹插入、移除驾荣、移動(dòng)等等都會(huì)更新stack外构。

那問題又來了,UiApplier是Composition的入?yún)⒉ブ溃烤故悄膫€(gè)對(duì)象來操作UiApplier了审编?

Composition

// androidx.compose.runtime.CompositionKt.class
fun Composition(
    applier: Applier<*>, // UiApplier
    parent: CompositionContext // Recomposer
): Composition =
    CompositionImpl(
        parent,
        applier
    )
internal class CompositionImpl(
    private val parent: CompositionContext, // Recomposer
    private val applier: Applier<*>, // UiApplier
    recomposeContext: CoroutineContext? = null
) : ControlledComposition {
    ... 

    // 保存所有視圖組合的信息(核心數(shù)據(jù)結(jié)構(gòu))
    private val slotTable = SlotTable()
    
    ...
  
    private val composer: ComposerImpl =
        ComposerImpl(
            applier = applier,
            parentContext = parent,
            slotTable = slotTable,
            abandonSet = abandonSet,
            changes = changes,
            composition = this
        ).also {
            parent.registerComposer(it)
        }
  
    ...
}

SlotTable是一個(gè)很大的話題,類似于 Gap Buffer (間隙緩沖區(qū)) 歧匈,技術(shù)有限這邊就不展開說了垒酬,官方可能覺得也很難理解,在知乎上說明了SlotTable執(zhí)行模式

所以可以說Composition是一個(gè)連接器勘究,把視圖樹(UiApplier)和調(diào)度程序(Recomposer)連接到一起矮湘,當(dāng)監(jiān)聽到數(shù)據(jù)(比如mutableState)變化或者添加視圖等等,Recomposer會(huì)通過Composition通知SlotTable更新視圖組合信息口糕、UiApplier更新視圖樹等等缅阳,在分析UiApplier的Text()底層實(shí)現(xiàn)源碼中,最終是創(chuàng)建了ReusableComposeNode景描,那在看看對(duì)應(yīng)的源碼十办。

@Composable inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
    noinline factory: () -> T,
    update: @DisallowComposableCalls Updater<T>.() -> Unit
) {
    if (currentComposer.applier !is E) invalidApplier()
    currentComposer.startReusableNode()
    if (currentComposer.inserting) {
        currentComposer.createNode { factory() }
    } else {
        currentComposer.useNode()
    }
    currentComposer.disableReusing()
    Updater<T>(currentComposer).update()
    currentComposer.enableReusing()
    currentComposer.endNode()
}

currentComposer就是Composition中的composer

調(diào)用startReusableNode()就是操作SlotReader,而SlotReader其實(shí)就是SlotTable的讀取控制器超棺,對(duì)應(yīng)的SlotTable也有SlotWriter寫入控制器向族。

調(diào)用createNode()就是操作UiApplier

WrappedComposition.setContent()

終于講到WrappedComposition.setContent(),它是一個(gè)帶有處理生命周期的包裹说搅,把AndroidComposeView和Composition包裹炸枣,當(dāng)AndroidComposeView被附加到ComposeView后,會(huì)添加LifecycleEventObserver弄唧,之后觸發(fā)生命周期Lifecycle.Event.ON_CREATE,會(huì)先調(diào)用連接器Composition的setContent()霍衫,再執(zhí)行調(diào)度程序Recomposer的composeInitial()候引,再調(diào)用連接器Composition的setContent() -> composeContent(),再調(diào)用連接器中的帶有SlotTable的composer.composeContent()敦跌,最終執(zhí)行invokeComposable()來組合我們的入口函數(shù)對(duì)象澄干。

調(diào)用鏈看起來很懵逼,關(guān)鍵是要理解Composition柠傍、composer麸俘、CompositionContext(Recomposer)、UiApplier每個(gè)對(duì)象的關(guān)系惧笛、職責(zé)和工作的傳遞从媚,最后再看看相關(guān)涉及的源碼。

// androidx.compose.ui.platform.WrappedComposition
private class WrappedComposition(
    val owner: AndroidComposeView,
    val original: Composition // 
) : Composition, LifecycleEventObserver {

    private var disposed = false
    private var addedToLifecycle: Lifecycle? = null
    private var lastContent: @Composable () -> Unit = {}

    override fun setContent(content: @Composable () -> Unit) {
        owner.setOnViewTreeOwnersAvailable {
            if (!disposed) {
                val lifecycle = it.lifecycleOwner.lifecycle
                lastContent = content
                if (addedToLifecycle == null) {
                    // 1. 初始化流程第一次會(huì)進(jìn)入
                    addedToLifecycle = lifecycle
                    // 2. 設(shè)置生命周期監(jiān)聽
                    lifecycle.addObserver(this)
                } else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
                    
                    // 4. 調(diào)用連接器的setContent
                    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) {
                            @OptIn(InternalComposeApi::class)
                            inspectionTable.add(currentComposer.compositionData)
                            currentComposer.collectParameterInformation()
                        }

                        LaunchedEffect(owner) { owner.keyboardVisibilityEventLoop() }
                        LaunchedEffect(owner) { owner.boundsUpdatesEventLoop() }
                                                
                        // 入口函數(shù)對(duì)象
                        CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
                            ProvideAndroidCompositionLocals(owner, content)
                        }
                    }
                }
            }
        }
    }

    override fun dispose() {
        if (!disposed) {
            disposed = true
            // 清空帶生命周期的包裹
            owner.view.setTag(R.id.wrapped_composition_tag, null)
            // 移除生命周期艦艇
            addedToLifecycle?.removeObserver(this)
        }
        original.dispose()
    }

    override val hasInvalidations get() = original.hasInvalidations
    override val isDisposed: Boolean get() = original.isDisposed

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            dispose()
        } else if (event == Lifecycle.Event.ON_CREATE) {
            if (!disposed) {
                // 3. 重新執(zhí)行
                setContent(lastContent)
            }
        }
    }
}
Composition -> original.setContent()
// androidx.compose.runtime.CompositionImpl
override fun setContent(content: @Composable () -> Unit) {
    check(!disposed) { "The composition is disposed" }
    this.composable = content
    // parent = Recomposer = ComposeView.compositionContext
    parent.composeInitial(this, composable)
}
Recomposer -> parent.composeInitial(this, composable)
// androidx.compose.runtime.Recomposer
internal override fun composeInitial(
    composition: ControlledComposition, // Recomposer
    content: @Composable () -> Unit // 入口函數(shù)對(duì)象
) {
    val composerWasComposing = composition.isComposing
    composing(composition, null) {
        // 走到連接器
        composition.composeContent(content)
    }
    ...
}
composition.composeContent(content)
// androidx.compose.runtime.CompositionImpl
override fun composeContent(content: @Composable () -> Unit) {
    synchronized(lock) {
        drainPendingModificationsForCompositionLocked()
        // 走到內(nèi)部composer
        composer.composeContent(takeInvalidations(), content)
    }
}
composer.composeContent(takeInvalidations(), content)
// androidx.compose.runtime.ComposerImpl
internal fun composeContent(
    invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
    content: @Composable () -> Unit // 入口函數(shù)對(duì)象
) {
    runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
    doCompose(invalidationsRequested, content)
}

private fun doCompose(
    invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
    content: (@Composable () -> Unit)? // 入口函數(shù)對(duì)象
) {
    runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
    trace("Compose:recompose") {
        snapshot = currentSnapshot()
        invalidationsRequested.forEach { scope, set ->
            val location = scope.anchor?.location ?: return
            invalidations.add(Invalidation(scope, location, set))
        }
        invalidations.sortBy { it.location }
        nodeIndex = 0
        var complete = false
        isComposing = true
        try {
            startRoot()
            // Ignore reads of derivedStatOf recalculations
            observeDerivedStateRecalculations(
                start = {
                    childrenComposing++
                },
                done = {
                    childrenComposing--
                },
            ) {
                if (content != null) {
                    startGroup(invocationKey, invocation)
                    // 真正處理入口函數(shù)對(duì)象的地方
                    invokeComposable(this, content)
                    endGroup()
                } else {
                    skipCurrentGroup()
                }
            }
            endRoot()
            complete = true
        } finally {
            isComposing = false
            invalidations.clear()
            providerUpdates.clear()
            if (!complete) abortRoot()
        }
    }
}

startRoot()患整、endGroup() 是操作SlotTable

最后再看看invokeComposable做了什么

internal fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
    @Suppress("UNCHECKED_CAST")
    val realFn = composable as Function2<Composer, Int, Unit>
    realFn(composer, 1)
}

原來最終把我們的入口函數(shù)對(duì)象強(qiáng)轉(zhuǎn)成Function2拜效,但是當(dāng)要去查看Function2是做什么的時(shí)候,沒辦法找到類各谚。

原因是這邊是compiler(基于kotlin的編譯器插件)做的事情紧憾,它把我們的入口函數(shù)進(jìn)行了修改,添加了一些參數(shù)昌渤,比如Int對(duì)應(yīng)的就是groupId赴穗,所以才能強(qiáng)轉(zhuǎn)成功。拿官方的例子來說明一下就能明白了。

@Composable
fun Counter() {
 var count by remember { mutableStateOf(0) }
 Button(
   text="Count: $count",
   onPress={ count += 1 }
 )
}
fun Counter($composer: Composer, groupId: Int) {
 $composer.start(groupId)
 var count by remember { mutableStateOf(0) }
 Button(
   text="Count: $count",
   onPress={ count += 1 }
 )
 $composer.end()
}

小結(jié)

初次看setContent源碼般眉,什么流程都記不住看不懂了赵,只知道N個(gè)setContent跳來跳去,當(dāng)把Composition煤篙、composer斟览、CompositionContext(Recomposer)UiApplier每個(gè)對(duì)象的關(guān)系辑奈、職責(zé)搞清楚之后苛茂,再對(duì)跳轉(zhuǎn)流程理清楚,就能發(fā)現(xiàn)具體的工作流程了鸠窗。

所分析的setContent()只是Compose Ui體系中的一小部分妓羊,發(fā)現(xiàn)分析完后,延伸出了更多的知識(shí)點(diǎn)稍计,比如SlotTable工作的流程和時(shí)間復(fù)雜度躁绸、LayoutNode和AndroidComposeView相關(guān)的測(cè)量繪制觸摸反饋、mutableState刷新機(jī)制等等臣嚣,還有分析過程中提出的一個(gè)問題Compose和傳統(tǒng)View/ViewGroup互相交互并且能在同一個(gè)activity混合開發(fā)嗎净刮?

參考資料

官方 深入詳解 Jetpack Compose | 實(shí)現(xiàn)原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市硅则,隨后出現(xiàn)的幾起案子淹父,更是在濱河造成了極大的恐慌,老刑警劉巖怎虫,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暑认,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡大审,警方通過查閱死者的電腦和手機(jī)蘸际,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來徒扶,“玉大人粮彤,你說我怎么就攤上這事】崂ⅲ” “怎么了驾诈?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)溶浴。 經(jīng)常有香客問我乍迄,道長(zhǎng),這世上最難降的妖魔是什么士败? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任闯两,我火速辦了婚禮褥伴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漾狼。我一直安慰自己重慢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布逊躁。 她就那樣靜靜地躺著似踱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稽煤。 梳的紋絲不亂的頭發(fā)上核芽,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音酵熙,去河邊找鬼轧简。 笑死,一個(gè)胖子當(dāng)著我的面吹牛匾二,可吹牛的內(nèi)容都是我干的哮独。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼察藐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼皮璧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起分飞,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤恶导,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后浸须,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邦泄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年删窒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顺囊。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肌索,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出特碳,到底是詐尸還是另有隱情诚亚,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布午乓,位于F島的核電站站宗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏益愈。R本人自食惡果不足惜梢灭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一夷家、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敏释,春花似錦库快、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜂大,卻和暖如春闽铐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背县爬。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工阳啥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人财喳。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓察迟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親耳高。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扎瓶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容