開(kāi)發(fā)網(wǎng)頁(yè)系統(tǒng)一般多少錢windows優(yōu)化大師官網(wǎng)
前言
本文是筆者學(xué)習(xí)Compose
是如何自動(dòng)觸發(fā)UI刷新的筆記,可能缺乏一定可讀性和教導(dǎo)性.(建議閱讀參考文獻(xiàn)更具啟發(fā)性)
使用以下BOM
作為研究環(huán)境.
composeBom = "2024.04.01"
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
我們看一下下面的程序,再點(diǎn)擊OneComposable
按鈕的時(shí)候?yàn)槭裁磧H僅TwoComposable
重組,其他的Composable
不會(huì)?背后是如何實(shí)現(xiàn)特定作用域定向刷新?
@Composable
fun MainCompose3() {Column {Logd("invoke MainCompose3")val displayState = remember { mutableIntStateOf(1) }OneComposable(displayState)TwoComposable(displayState)}
}@Composable
fun TwoComposable(flagState: MutableState<Int>) {Logd("invoke TwoComposable")Text("hello world ${flagState.value}")
}@Composable
fun OneComposable(flagState: MutableState<Int>) {Logd("invoke OneComposable")Button(onClick = {flagState.value = ++flagState.value}) {Text("Change flagState")}}
fun Logd(msg:String){Log.d("test",msg)
}
當(dāng)點(diǎn)擊OneComposable
的按鈕重組輸出:
invoke TwoComposable
使用原始View模擬自動(dòng)刷新
我們借用原始 View
系統(tǒng)配合快照
完成一個(gè)類似 Compose
自動(dòng)局部刷新.
建議讀者先自行閱讀快照
文獻(xiàn):
一文看懂 Jetpack Compose 快照系統(tǒng)
我們布局如下:
//MainActivity.kt
class MainActivity : ComponentActivity() {//private val displayOneState = mutableIntStateOf(1)private val displayTwoState = mutableIntStateOf(1)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT)val rootView = FrameLayout(this)setContentView(rootView, layoutParams)//Composer是我們自定義的Composer.setContentView(rootView){//LinearLayoutOneColumnComposable(Composer, rootView) { view ->//Texview 1 read and show displayOneStateOneTvComposable(Composer, view, displayOneState)//Textview 2 => read and show displayTwoStateTwoTvComposable(Composer, view, displayTwoState)//Button => modify displayOneStateOneBtnComposable(Composer, view, displayOneState)}}}
}
布局展示如下:
多次點(diǎn)擊按鈕后Texview 1
更新文案
我們首先需要了解Composer#setContentView
做什么.
//MainActivity.kt
object Composer {fun setContentView(rootView:ViewGroup,content: (ViewGroup) -> Unit){//創(chuàng)建一個(gè)快照,用于感知content對(duì)于 state 的讀取,val snapshot = Snapshot.takeSnapshot(readObserver = { mutableState ->//每次在 content 函數(shù)中任意讀取 state 都會(huì)回調(diào)到此.//content有多個(gè)函數(shù)OneTvComposable,TwoTvComposable都會(huì)讀取不同的state.//我們?nèi)绾螛?biāo)記當(dāng)前state被那個(gè)函數(shù)讀取?})//進(jìn)入快照中enter函數(shù)才可感知state讀寫snapshot.enter {content.invoke(rootView)}}
}
為了在readObserver
回調(diào),為感知是那個(gè)函數(shù)讀取,我們?cè)O(shè)計(jì)一個(gè)棧算法,每次調(diào)用xxxxComposable
函數(shù)的時(shí)候構(gòu)建一個(gè)UpdateScope
,并壓入棧中.在函數(shù)結(jié)束的時(shí)候彈出棧.
為了方便我們把UpdateScope
稱為更新域.
我們首先查看讀取棧的代碼:
val state2Scope = MutableScatterMap<Any, MutableSet<UpdateScope>>()
val scopeStack: Deque<UpdateScope> = java.util.ArrayDeque<UpdateScope>()Snapshot.takeSnapshot(readObserver = { mutableState ->//一個(gè)state 可能會(huì)被多個(gè)UpdateScope讀取var updateScopes = state2Scope[mutableState]if (updateScopes.isNullOrEmpty()) {updateScopes = mutableSetOf()state2Scope[mutableState] = updateScopes}//查看棧頂?shù)膗pdateScopes然后放入一個(gè)映射中.//這樣我們就可以知道 state 更新了哪些 updateScopes 需要被重新重組val updateScope = scopeStack.peek();if (updateScope != null) {updateScopes.add(updateScope)}})
//略
最后我們看看如何構(gòu)造這些updateScopes
棧對(duì)象.
//id 標(biāo)記某個(gè)composable函數(shù)方便索引
//update回調(diào)composable在數(shù)據(jù)更新的時(shí)候
data class UpdateScope(val id:Int, val update:()->Unit)
fun OneTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {// 創(chuàng)建scope 然后賦予一個(gè)唯一 id 方便查找.val scope = UpdateScope(0x00001){//數(shù)據(jù)更新的時(shí)候OneTvComposable(composer,parent,state)}scopeStack.push(scope)//創(chuàng)建一個(gè) Textview 去展示val viewId = "OneText"MyText(viewId, "${state.intValue}", parent)scopeStack.pop()
}
其他函數(shù)類似就不演示,我們圖展示下列Composable
代碼運(yùn)行流程
OneColumnComposable(Composer, rootView) { view ->OneTvComposable(Composer, view, displayOneState)TwoTvComposable(Composer, view, displayTwoState)OneBtnComposable(Composer, view, displayOneState)
}
OneColumnComposable
函數(shù)內(nèi)部不會(huì)讀取任何狀態(tài),所以僅僅會(huì)壓入棧不會(huì)觸發(fā) snapshot 讀取.圖示例如下:
fun OneColumnComposable(composer: Composer,parent: ViewGroup,content: (ViewGroup) -> Unit
) {// 創(chuàng)建scope 然后賦予一個(gè)唯一 id 方便查找.val scope = UpdateScope(0x00004){//數(shù)據(jù)更新的時(shí)候OneColumnComposable(composer,parent,state)}scopeStack.push(scope)//創(chuàng)建一個(gè) LinearLayoutMyColumn("oneColumn", parent, { view ->content.invoke(view)})scopeStack.pop()}
運(yùn)行OneTvComposable
,會(huì)壓入一個(gè)新的 Scope
,由于在這個(gè)函數(shù)讀取了 state
,會(huì)觸發(fā) snapshot 讀取回調(diào),更新updateScope
映射信息
運(yùn)行TwoTvComposable
時(shí),OneTvComposable
會(huì)彈出之前的棧.會(huì)壓入一個(gè)新的 Scope
,由于在這個(gè)函數(shù)讀取了 state
,會(huì)觸發(fā)snapshot
讀取回調(diào),更新updateScope
映射信息
fun TwoTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {// 創(chuàng)建scope 然后賦予一個(gè)唯一 id 方便查找.val scope = UpdateScope(0x00002){//數(shù)據(jù)更新的時(shí)候TwoTvComposable(composer,parent,state)}scopeStack.push(scope)//創(chuàng)建一個(gè) Textview 去展示val viewId = "TwoText"MyText(viewId, "${state.intValue}", parent)scopeStack.pop()
}
OneBtnComposable
函數(shù)并不會(huì)讀取 state 而是簡(jiǎn)單的寫入.所以并不會(huì)影響 state2Scope
fun OneBtnComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {// 創(chuàng)建scope 然后賦予一個(gè)唯一 id 方便查找.val scope = UpdateScope(0x00003){//數(shù)據(jù)更新的時(shí)候OneBtnComposable(composer,parent,state)}val viewId = "OneBtn"MyButton(viewId, "changeState", parent, {state.intValue += 1})scopeStack.pop()}
當(dāng)OneBtnComposable
函數(shù)結(jié)束的時(shí)候OneColumnComposable
也對(duì)應(yīng)結(jié)束了函數(shù)周期.所有 信息將會(huì)從scopestack
將會(huì)彈出
現(xiàn)在我們有了state2Scope
存儲(chǔ)信息,在 state 更改時(shí)調(diào)用對(duì)應(yīng)的UpdateScope
的回調(diào)即可完成更新.
Snapshot.registerGlobalWriteObserver {//全局作用于的快照被寫入的時(shí)候回調(diào)//調(diào)用通知.此時(shí)會(huì)觸發(fā)registerApplyObserver回調(diào)Snapshot.sendApplyNotifications()
}
Snapshot.registerApplyObserver { anies, snapshot ->for (any in anies) {//any 就是我們的 stateval updateScopes = state2Scope[any]//重新調(diào)用函數(shù)觸發(fā)更新updateScopes.update()}
}
上面的設(shè)計(jì)方案有一個(gè)比較致命的性能問(wèn)題比如我們看一下下面的代碼,根布局會(huì)根據(jù)backgroundColorState
修改自身背景顏色
private val backgroundColorState = mutableIntStateOf(Color.BLUE)
//OneColumnComposable會(huì)讀取backgroundColorState變量去設(shè)置背景色
OneColumnComposable(Composer, rootView,backgroundColorState) { view ->OneTvComposable(Composer, view, displayOneState)TwoTvComposable(Composer, view, displayTwoState)//按鈕會(huì)修改背景色 OneBtnComposable(Composer, view, backgroundColorState)
}
fun OneColumnComposable(composer: Composer,parent: ViewGroup,content: (ViewGroup) -> Unit
) {// 創(chuàng)建scope 然后賦予一個(gè)唯一 id 方便查找.val scope = UpdateScope(0x00004){//數(shù)據(jù)更新的時(shí)候OneColumnComposable(composer,parent,state)}scopeStack.push(scope)//創(chuàng)建一個(gè) LinearLayout,并制定背景色顏色MyColumn("oneColumn", parent,backgroundColorState.value, { view ->content.invoke(view)})scopeStack.pop()
}
這時(shí)候觸發(fā)切換顏色的時(shí)候我們期待僅有OneColumnComposable
會(huì)被回調(diào).但是實(shí)際上OneColumnComposable
,OneTvComposable
,TwoTvComposable
,OneBtnComposable
全部會(huì)重新觸發(fā).我們可以在建立一個(gè)新的樹(shù)稱為 Group
樹(shù),這個(gè)樹(shù)中每個(gè)節(jié)點(diǎn)存儲(chǔ)是否Dirty,然后更新的時(shí)候選擇性更新判斷.
樹(shù)中節(jié)點(diǎn)如下
data class Group(val id: Int,var parent: Group?,val child: MutableScatterMap<Int, Group> = mutableScatterMapOf()
) {//標(biāo)記節(jié)點(diǎn)是否需要更新var dirtyFlag: Int = DIRTY_STATE_INITcompanion object {val ROOT_NODE = Group(0x0000, null, mutableScatterMapOf())//節(jié)點(diǎn)未重組過(guò),需要重組val DIRTY_STATE_INIT = 0//節(jié)點(diǎn)是干凈的不需要被重組val DIRTY_STATE_CLEAN = DIRTY_STATE_INIT + 1//節(jié)點(diǎn)數(shù)據(jù)過(guò)時(shí)需要重組val DIRTY_STATE_DECAY = DIRTY_STATE_CLEAN + 1}override fun toString(): String {return """ self ${this.id} -> child [${child.joinToString(transform = { ke, v -> v.toString() })}]""".trimMargin()}
}
我們可以在創(chuàng)建scope
棧的時(shí)候結(jié)合一起構(gòu)建這個(gè) group
樹(shù).我們舉例OneTvComposable
來(lái)說(shuō)明.我們順帶把所有這類任務(wù)的代碼放入一個(gè)叫Composer
對(duì)象中
fun OneTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {//創(chuàng)建一個(gè) scope 棧對(duì)象,并且創(chuàng)建一個(gè) group 樹(shù)節(jié)點(diǎn)val group = composer.startGroup(0x00001)if (group.change()) {Logd("invoke OneTvComposable")val viewId = "OneText"MyText(viewId, "${state.intValue}", parent)} else {}//結(jié)束的時(shí)候時(shí)候我們我們彈出 scope 棧對(duì)象,并維護(hù) group 樹(shù)節(jié)點(diǎn)group.endScope {OneTvComposable(composer, parent, state)}
}//Composer對(duì)象內(nèi)嵌函數(shù)
class Composer{//標(biāo)記由于 state 寫入需要重新刷新的 groupval dirtyGroup: MutableScatterMap<Int, Group> = mutableScatterMapOf<Int, Group>()fun startGroup(composableId: Int): UpdateScope {//調(diào)用startGroup此 Group已經(jīng)被重組,移除標(biāo)記val dirtyGroup = dirtyGroup.remove(composableId)//構(gòu)建好 group 樹(shù)節(jié)點(diǎn),這個(gè)樹(shù)用于判斷數(shù)據(jù)變化時(shí)更新策略.提升重組性能val group = if (dirtyGroup == null) {val parentGroup =scopeStack.peek()?.group ?: rootNodeval group = dirtyGroup ?: parentGroup.child[composableId] ?: Group(composableId,scopeStack.peek()?.group,mutableScatterMapOf())parentGroup.child[composableId] = groupgroup} else {dirtyGroup}//構(gòu)造 scope 棧對(duì)象,方便感知刷新域val updateScope = UpdateScope(composableId, group, null)scopeStack.push(updateScope)return updateScope}//彈出棧,并重新標(biāo)記 group 為干凈fun endScope(update: (() -> Unit)) {this.update = updateComposer.scopeStack.pop()group.dirtyFlag = DIRTY_STATE_CLEAN}
}
最后我們?cè)诓殚喯聦懭牖卣{(diào)處的處理.
Snapshot.registerGlobalWriteObserver {Snapshot.sendApplyNotifications()
}
Snapshot.registerApplyObserver { anies, snapshot ->for (any in anies) {val updateScopes = state2Scope[any]updateScopes?.forEach { scope ->dirtyGroup[scope.id] = (scope.group)//僅標(biāo)記被污染的 group,可以避免子group也過(guò)度參與.scope.group.dirtyFlag = DIRTY_STATE_DECAYupdateFrame(scope)}}
}
//開(kāi)始重組
private fun updateFrame(updateScope: UpdateScope) {while (scopeStack.isNotEmpty()) {val popScope = scopeStack.pop()if (updateScope == popScope) {break}}updateScope.update?.invoke()
}
上面便是一個(gè)簡(jiǎn)易版本 View 下模擬 compose 流程.Group
樹(shù)用數(shù)據(jù)變時(shí)怎么樣刷新,UpdateSope
用于在哪刷新,而Composable
描述了怎么樣的一個(gè) View
樹(shù)
最后我們貼出完整相關(guān)代碼
data class Group(val id: Int,var parent: Group?,val child: MutableScatterMap<Int, Group> = mutableScatterMapOf()
) {var dirtyFlag: Int = DIRTY_STATE_INITcompanion object {val ROOT_NODE = Group(0x0000, null, mutableScatterMapOf())val DIRTY_STATE_INIT = 0val DIRTY_STATE_CLEAN = DIRTY_STATE_INIT + 1val DIRTY_STATE_DECAY = DIRTY_STATE_CLEAN + 1}override fun toString(): String {return """ self ${this.id} -> child [${child.joinToString(transform = { ke, v -> v.toString() })}]""".trimMargin()}
}class UpdateScope(val id: Int, val group: Group, var update: (() -> Unit)? = null) {override fun equals(other: Any?): Boolean {if (other !is UpdateScope) {return false}return other.id == this.id}override fun hashCode(): Int {return this.id}fun endScope(update: (() -> Unit)) {this.update = updateComposer.scopeStack.pop()group.dirtyFlag = DIRTY_STATE_CLEAN}fun change(): Boolean {return group.dirtyFlag == DIRTY_STATE_DECAY || group.dirtyFlag == DIRTY_STATE_INIT}
}object Composer {val state2Scope = MutableScatterMap<Any, MutableSet<UpdateScope>>()val scopeStack: Deque<UpdateScope> = java.util.ArrayDeque<UpdateScope>()val dirtyGroup: MutableScatterMap<Int, Group> = mutableScatterMapOf<Int, Group>()val rootNode: Group = ROOT_NODEinit {Snapshot.registerGlobalWriteObserver {Snapshot.sendApplyNotifications()}Snapshot.registerApplyObserver { anies, snapshot ->for (any in anies) {val updateScopes = state2Scope[any]updateScopes?.forEach { scope ->dirtyGroup[scope.id] = (scope.group)scope.group.dirtyFlag = DIRTY_STATE_DECAYupdateFrame(scope)}}}}private fun updateFrame(updateScope: UpdateScope) {while (scopeStack.isNotEmpty()) {val popScope = scopeStack.pop()if (updateScope == popScope) {break}}updateScope.update?.invoke()}fun startGroup(composableId: Int): UpdateScope {val dirtyGroup = dirtyGroup.remove(composableId)val group = if (dirtyGroup == null) {val parentGroup =scopeStack.peek()?.group ?: rootNodeval group = dirtyGroup ?: parentGroup.child[composableId] ?: Group(composableId,scopeStack.peek()?.group,mutableScatterMapOf())parentGroup.child[composableId] = groupgroup} else {dirtyGroup}val updateScope = UpdateScope(composableId, group, null)scopeStack.push(updateScope)return updateScope}fun setContentView(rootView: ViewGroup, content: (ViewGroup) -> Unit) {val snapshot = Snapshot.takeSnapshot(readObserver = { mutableState ->var updateScopes = state2Scope[mutableState]if (updateScopes.isNullOrEmpty()) {updateScopes = mutableSetOf()state2Scope[mutableState] = updateScopes}val updateScope = scopeStack.peek();if (updateScope != null) {updateScopes.add(updateScope)}})snapshot.enter {content.invoke(rootView)}}
}class MainActivity : ComponentActivity() {private val displayOneState = mutableIntStateOf(1)private val displayTwoState = mutableIntStateOf(1)private val backgroundColorState = mutableIntStateOf(android.graphics.Color.BLUE)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT)val rootView = FrameLayout(this)setContentView(rootView, layoutParams)Composer.setContentView(rootView) {OneColumnComposable(Composer, rootView, backgroundColorState) { view ->OneTvComposable(Composer, view, displayOneState)TwoTvComposable(Composer, view, displayTwoState)OneBtnComposable(Composer, view, backgroundColorState)}}Log.d("fmy", "tree : ${Composer.rootNode}")}fun OneColumnComposable(composer: Composer,parent: ViewGroup,backgroundColorState: MutableIntState,content: (ViewGroup) -> Unit) {val group = composer.startGroup(0x00004)if (group.change()) {Logd("invoke OneColumnComposable")MyColumn("oneColumn", parent, backgroundColorState.intValue) { view ->content.invoke(view)}} else {}group.endScope {OneColumnComposable(composer, parent, this.backgroundColorState, content)}}fun MyColumn(viewId: String,parent: ViewGroup,backgroundColor: Int,content: (ViewGroup) -> Unit) {val llView = parent.findViewWithTag<LinearLayout>(viewId) ?: LinearLayout(this)if (llView.parent == null) {llView.tag = viewIdval layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)parent.addView(llView, layoutParams)llView.orientation = LinearLayout.VERTICAL}
// llView.setBackgroundResource(R.color.teal_200)llView.setBackgroundColor(backgroundColor)content.invoke(llView)}fun MyText(viewId: String, content: String, parent: ViewGroup) {val oldText = parent.findViewWithTag<TextView>(viewId)val textView = if (oldText == null) {val textView = TextView(this)textView.tag = viewIdparent.addView(textView)textView} else {oldText}textView.text = content}fun MyButton(viewId: String, content: String, parent: ViewGroup, click: () -> Unit) {val oldBtn = parent.findViewWithTag<Button>(viewId)val btn = if (oldBtn == null) {val btn = Button(this)btn.tag = viewIdparent.addView(btn)btn} else {oldBtn}btn.text = contentbtn.setOnClickListener { click.invoke() }}fun OneTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {//創(chuàng)建一個(gè) scope 棧對(duì)象,并且創(chuàng)建一個(gè) group 樹(shù)節(jié)點(diǎn)val group = composer.startGroup(0x00001)if (group.change()) {Logd("invoke OneTvComposable")val viewId = "OneText"MyText(viewId, "${state.intValue}", parent)} else {}//結(jié)束的時(shí)候時(shí)候我們我們彈出 scope 棧對(duì)象,并維護(hù) group 樹(shù)節(jié)點(diǎn)group.endScope {OneTvComposable(composer, parent, state)}}fun TwoTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {val group = composer.startGroup(0x00002)if (group.change()) {val viewId = "TwoText"Logd("invoke TwoTvComposable")MyText(viewId, "${state.intValue}", parent)} else {}group.endScope {TwoTvComposable(composer, parent, state)}}fun OneBtnComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {val group = composer.startGroup(0x00003)if (group.change()) {val id = "OneBtn"Logd("invoke OneBtnComposable")MyButton(id, "changeState", parent, {
// state.intValue += 1state.intValue = Color.RED})} else {}group.endScope {OneBtnComposable(composer, parent, state)}}}
Compose 源碼閱讀
我們有如下Demo
作為講解說(shuō)明.
一個(gè)按鈕
和一個(gè)文本
,每次點(diǎn)擊按鈕
觸發(fā)數(shù)字單調(diào)遞增
示例代碼如下:
//MainActivity.kt
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Home()}}
}
@Composable
private fun Home() {ComposeDemoTheme {Surface {Column {val displayState = remember { mutableIntStateOf(1) }MainCompose(displayState)Button(onClick = {displayState.intValue = ++displayState.intValue}) {Text("increase displayState")}}}}
}
@Composable
fun MainCompose(displayState: MutableState<Int>) {val value = displayState.valueText("display $value")
}@Composable
@Preview
fun HomePreview(){Home()
}
本文需要有基礎(chǔ)的快照
和SlotTable
概念以避免重復(fù)造輪子.
手?jǐn)] View 下局部自動(dòng)更新
MainCompose
原始的函數(shù)會(huì)在編譯后變?yōu)橐韵麓a.
@Composable@ComposableTarget(applier = "androidx.compose.ui.UiComposable")public static final void MainCompose(@NotNull final MutableState displayState, @Nullable Composer $composer, final int $changed) {//每一個(gè) compose 都會(huì)構(gòu)建一個(gè) Group,最終Group也會(huì)組成一個(gè)樹(shù).(一定要注意這個(gè)不是渲染樹(shù) LayoutNode,Compose 里有多顆樹(shù),這顆樹(shù)用做數(shù)據(jù)處理) //而startRestartGroup也會(huì)創(chuàng)建一個(gè) Group 放入樹(shù)中$composer = $composer.startRestartGroup(-1327587884);ComposerKt.sourceInformation($composer, "C(MainCompose)47@1341L22:MainActivity.kt#ffoge4");//結(jié)合一些數(shù)據(jù)判斷當(dāng)前是否可以跳過(guò)重組int $dirty = $changed;if (($changed & 14) == 0) {$dirty |= $composer.changed(displayState) ? 4 : 2;}//如果當(dāng)前 Composeable是skippable那么會(huì)結(jié)合當(dāng)前入?yún)⑴袛嗍欠衲芴^(guò)//skippable本文后面會(huì)簡(jiǎn)單介紹if (($dirty & 11) == 2 && $composer.getSkipping()) {$composer.skipToGroupEnd();} else {if (ComposerKt.isTraceInProgress()) {ComposerKt.traceEventStart(-1327587884, $dirty, -1, "com.example.composedemo.MainCompose (MainActivity.kt:45)");}//如果需要重組那么進(jìn)行int value = ((Number)displayState.getValue()).intValue();TextKt.Text--4IGK_g("display " + value, (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, 0, (Function1)null, (TextStyle)null, $composer, 0, 0, 131070);if (ComposerKt.isTraceInProgress()) {ComposerKt.traceEventEnd();}}//標(biāo)記當(dāng)前 Group 在樹(shù)中結(jié)束,并返回一個(gè) Compose 更新域(ScopeUpdateScope).//ScopeUpdateScope會(huì)在displayState更新時(shí)調(diào)用updateScope進(jìn)而發(fā)生重組ScopeUpdateScope var5 = $composer.endRestartGroup();if (var5 != null) {var5.updateScope((Function2)(new Function2() {public final void invoke(Composer $composer, int $force) {//如果數(shù)據(jù)變更會(huì)會(huì)回調(diào)MainActivityKt.MainCompose(displayState, $composer, RecomposeScopeImplKt.updateChangedFlags($changed | 1));}// $FF: synthetic method// $FF: bridge methodpublic Object invoke(Object p1, Object p2) {this.invoke((Composer)p1, ((Number)p2).intValue());return Unit.INSTANCE;}}));}}
你會(huì)驚訝的發(fā)現(xiàn)函數(shù)背后做的事情和我們自己實(shí)現(xiàn)在 View
下差不多.
我們這里額外補(bǔ)充一個(gè)細(xì)節(jié),你會(huì)注意到有一個(gè)$composer.getSkipping()
函數(shù)才會(huì)判斷當(dāng)前 Composeable
是否會(huì)跳過(guò),否則一定會(huì)觸發(fā)重組.
那么時(shí)候函數(shù)getSkipping
才為 true 呢?
Compose 編譯器會(huì)為每個(gè)Composable
做一個(gè)標(biāo)記.如果利用可以利用入?yún)⒑椭皞魅雲(yún)?shù)判斷相等那么可以被標(biāo)記skippable
.
我們比較下下面的兩個(gè)函數(shù)是否都可以被標(biāo)記skippable
?
//可以被標(biāo)記skippable,因?yàn)閐isplayState數(shù)值可以取出來(lái)和之前的比較
@Composable
fun MainCompose(displayState: MutableState<Int>) {val value = displayState.valueText("display $value")
}//不可以被標(biāo)記skippable,因?yàn)閘ist的實(shí)例可以比較,但是內(nèi)部的內(nèi)容和順序不可推斷
@Composable
fun MainCompose2(list: MutableList<String>) {Text("display $${list.joinToString { it }}")
}
相關(guān)具體知識(shí)點(diǎn)建議閱讀
what-do-the-stable-and-immutable-annotations-mean-in-jetpack-compose
有相關(guān)工具可以打印出編譯視角下函數(shù)結(jié)構(gòu),這里直接給出結(jié)果:
//標(biāo)記skippable
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MainCompose(stable displayState: MutableState<Int>
)
//不標(biāo)記skippable,這個(gè)函數(shù)被重組的時(shí)候一定會(huì)重新觸發(fā).
restartable scheme("[androidx.compose.ui.UiComposable]") fun MainCompose2(unstable list: MutableList<String>
)
我們看下 MainCompose2
被編譯后的代碼是不會(huì)存在skipToGroupEnd
函數(shù)的調(diào)用.重組時(shí)直接觸發(fā)不存在跳過(guò)邏輯.
@Composable
fun MainCompose2(list: MutableList<String>) {Text("display $${list.joinToString { it }}")
}@Composable@ComposableTarget(applier = "androidx.compose.ui.UiComposable")public static final void MainCompose2(@NotNull final List list, @Nullable Composer $composer, final int $changed) {$composer = $composer.startRestartGroup(1711764239);ComposerKt.sourceInformation($composer, "C(MainCompose2)51@1428L44:MainActivity.kt#ffoge4");if (ComposerKt.isTraceInProgress()) {ComposerKt.traceEventStart(1711764239, $changed, -1, "com.example.composedemo.MainCompose2 (MainActivity.kt:50)");}TextKt.Text--4IGK_g("display $" + CollectionsKt.joinToString$default((Iterable)list, (CharSequence)null, (CharSequence)null, (CharSequence)null, 0, (CharSequence)null, (Function1)null.INSTANCE, 31, (Object)null), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, 0, (Function1)null, (TextStyle)null, $composer, 0, 0, 131070);if (ComposerKt.isTraceInProgress()) {ComposerKt.traceEventEnd();}ScopeUpdateScope var3 = $composer.endRestartGroup();if (var3 != null) {var3.updateScope((Function2)(new Function2() {public final void invoke(Composer $composer, int $force) {MainActivityKt.MainCompose2(list, $composer, RecomposeScopeImplKt.updateChangedFlags($changed | 1));}// $FF: synthetic method// $FF: bridge methodpublic Object invoke(Object p1, Object p2) {this.invoke((Composer)p1, ((Number)p2).intValue());return Unit.INSTANCE;}}));}}
我們 Compose
下的startRestartGroup
是如何實(shí)現(xiàn),
//Composer.ktclass ComposerImpl(@ComposeCompilerApioverride fun startRestartGroup(key: Int): Composer {//創(chuàng)造一個(gè) Group 樹(shù)節(jié)點(diǎn),由于這塊比較復(fù)雜不展開(kāi)細(xì)說(shuō)start(key, null, GroupKind.Group, null)//創(chuàng)建一個(gè)重組域addRecomposeScope()return this}//創(chuàng)建一個(gè)重組域放入棧中private fun addRecomposeScope() {//...略val scope = RecomposeScopeImpl(composition as CompositionImpl)invalidateStack.push(scope)//...略}@ComposeCompilerApioverride fun endRestartGroup(): ScopeUpdateScope? {//...略//彈出棧val scope = if (invalidateStack.isNotEmpty()) invalidateStack.pop()//...略}}
我們最后官方源碼中,入口快照 take
函數(shù)調(diào)用處如下所示
//ReComposer.ktclass Recomposer{private inline fun <T> composing(composition: ControlledComposition,modifiedValues: IdentityArraySet<Any>?,block: () -> T): T {val snapshot = Snapshot.takeMutableSnapshot(readObserverOf(composition), writeObserverOf(composition, modifiedValues))try {return snapshot.enter(block)} finally {applyAndCheck(snapshot)}}
}
我們首先看readObserverOf
實(shí)現(xiàn)
//Composition.kt
//以 state 為 key,RecomposeScopeImpl為 value
//value內(nèi)部還有一層List封裝,因?yàn)?state 可以映射多個(gè)RecomposeScopeImpl
private val observations = ScopeMap<RecomposeScopeImpl>()override fun recordReadOf(value: Any) {//value 就是 state 對(duì)象//currentRecomposeScope就是更新域composer.currentRecomposeScope?.let {//存儲(chǔ)state 和RecomposeScopeImpl關(guān)系observations.add(value, it)}
}internal val currentRecomposeScope: RecomposeScopeImpl?
//查閱棧頂 scopeget() = invalidateStack.let {if (childrenComposing == 0 && it.isNotEmpty()) it.peek() else null}
封裝的ScopeMap
如下:
package androidx.compose.runtime.collectioninternal class ScopeMap<T : Any> {val map = mutableScatterMapOf<Any, Any>()val size get() = map.size//內(nèi)部會(huì)構(gòu)建 Set 集合放入多個(gè) value 去對(duì)應(yīng)一個(gè) keyfun add(key: Any, scope: T) {map.compute(key) { _, value ->when (value) {null -> scopeis MutableScatterSet<*> -> {@Suppress("UNCHECKED_CAST")(value as MutableScatterSet<T>).add(scope)value}else -> {if (value !== scope) {val set = MutableScatterSet<T>()@Suppress("UNCHECKED_CAST")set.add(value as T)set.add(scope)set} else {value}}}}}
}
我們知道快照有兩個(gè)作用域
一個(gè)全局的和 snapshot.enter
后綁定的. 而我們業(yè)務(wù)中往往在全局作用域去寫入state,所以本文我們先不閱讀writeObserverOf
代碼.(如果對(duì)快照概念模糊建議閱讀參考文獻(xiàn))
在 Compose
全局寫入觀察位于如下代碼中:
//Recomposer.kt
private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
) {//...Snapshot.registerApplyObserver { changed, _ ->//這里 Lamba最后的deriveStateLocked會(huì)返回一個(gè)協(xié)程的Continuation//Continuation.resume 調(diào)用會(huì)恢對(duì)應(yīng)協(xié)程繼續(xù)運(yùn)行synchronized(stateLock) {if (_state.value >= State.Idle) {changed.fastForEach {//it 是 state 對(duì)象//將所有被修改 state 放入集合中snapshotInvalidations.add(it)}//最后通知某個(gè)協(xié)程函數(shù),去觸發(fā)重組deriveStateLocked()} else null}?.resume(Unit)}//...
}private var workContinuation: CancellableContinuation<Unit>? = null
private fun deriveStateLocked(): CancellableContinuation<Unit>? {return if (newState == State.PendingWork) {//這里高階函數(shù)的作用是先workContinuation返回,再將workContinuation設(shè)置為 nullworkContinuation.also {workContinuation = null}} else null
}
我們通過(guò)上面的分析workContinuation
賦值點(diǎn)就是就是Compose開(kāi)始重組核心點(diǎn)
//Composer.kt
suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->//..略//為簡(jiǎn)化流程shouldKeepRecomposing可以視為永遠(yuǎn)為 truewhile (shouldKeepRecomposing) {//判斷當(dāng)前是否dirty 的 scope,如果沒(méi)有那么將當(dāng)前協(xié)程掛起,并將continuation 賦值給workContinuation//可以簡(jiǎn)單判斷snapshotInvalidations為空就執(zhí)行掛起awaitWorkAvailable()//等候下一個(gè) VSYNC 回調(diào)執(zhí)行實(shí)際重組.parentFrameClock.withFrameNanos { frameTime ->//這里會(huì)取出 dirty 的scope 開(kāi)始進(jìn)行重組工作//...略//toRecompose是一個(gè)CompositionImpl集合.//Mainwhile (toRecompose.isNotEmpty() || toInsert.isNotEmpty()) {try {toRecompose.fastForEach { composition ->alreadyComposed.add(composition)//最終會(huì)取出對(duì)應(yīng) scope回調(diào) 遞歸回調(diào)函數(shù)performRecompose(composition, modifiedValues)?.let {toApply += it}}} catch (e: Exception) {processCompositionError(e, recoverable = true)clearRecompositionState()return@withFrameNanos} finally {toRecompose.clear()}}}}
我們最后看看awaitWorkAvailable
相關(guān)代碼
//Recomposer.ktprivate val hasSchedulingWork: Booleanget() = synchronized(stateLock) {//是否有 dirty 的 scopesnapshotInvalidations.isNotEmpty() ||compositionInvalidations.isNotEmpty() ||hasBroadcastFrameClockAwaitersLocked}private suspend fun awaitWorkAvailable() {if (!hasSchedulingWork) {suspendCancellableCoroutine<Unit> { co ->synchronized(stateLock) {//如果有 dirty 的數(shù)據(jù)那么直接恢復(fù)協(xié)程完成重組if (hasSchedulingWork) {co} else {//掛起協(xié)程workContinuation = conull}}?.resume(Unit)}}
}
參考
一文看懂 Jetpack Compose 快照系統(tǒng)
探索 Jetpack Compose 內(nèi)核:深入 SlotTable 系統(tǒng)
what-do-the-stable-and-immutable-annotations-mean-in-jetpack-compose