长沙高端网站建设服务,广州网站制作哪家全面,招商网站开发,网站制作小常识前言
本文是笔者学习Compose是如何自动触发UI刷新的笔记,可能缺乏一定可读性和教导性.(建议阅读参考文献更具启发性)
使用以下BOM作为研究环境.
composeBom 2024.04.01
androidx-compose-bom { group androidx.compose, name compose-bom…前言
本文是笔者学习Compose是如何自动触发UI刷新的笔记,可能缺乏一定可读性和教导性.(建议阅读参考文献更具启发性)
使用以下BOM作为研究环境.
composeBom 2024.04.01
androidx-compose-bom { group androidx.compose, name compose-bom, version.ref composeBom }我们看一下下面的程序,再点击OneComposable按钮的时候为什么仅仅TwoComposable重组,其他的Composable不会?背后是如何实现特定作用域定向刷新?
Composable
fun MainCompose3() {Column {Logd(invoke MainCompose3)val displayState remember { mutableIntStateOf(1) }OneComposable(displayState)TwoComposable(displayState)}
}Composable
fun TwoComposable(flagState: MutableStateInt) {Logd(invoke TwoComposable)Text(hello world ${flagState.value})
}Composable
fun OneComposable(flagState: MutableStateInt) {Logd(invoke OneComposable)Button(onClick {flagState.value flagState.value}) {Text(Change flagState)}}
fun Logd(msg:String){Log.d(test,msg)
}当点击OneComposable的按钮重组输出:
invoke TwoComposable使用原始View模拟自动刷新
我们借用原始 View 系统配合快照完成一个类似 Compose 自动局部刷新. 建议读者先自行阅读快照文献: 一文看懂 Jetpack Compose 快照系统 我们布局如下: //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)}}}
} 布局展示如下: 多次点击按钮后Texview 1更新文案 我们首先需要了解Composer#setContentView做什么.
//MainActivity.kt
object Composer {fun setContentView(rootView:ViewGroup,content: (ViewGroup) - Unit){//创建一个快照,用于感知content对于 state 的读取,val snapshot Snapshot.takeSnapshot(readObserver { mutableState -//每次在 content 函数中任意读取 state 都会回调到此.//content有多个函数OneTvComposable,TwoTvComposable都会读取不同的state.//我们如何标记当前state被那个函数读取?})//进入快照中enter函数才可感知state读写snapshot.enter {content.invoke(rootView)}}
}为了在readObserver回调,为感知是那个函数读取,我们设计一个栈算法,每次调用xxxxComposable 函数的时候构建一个UpdateScope,并压入栈中.在函数结束的时候弹出栈. 为了方便我们把UpdateScope称为更新域.
我们首先查看读取栈的代码: val state2Scope MutableScatterMapAny, MutableSetUpdateScope()
val scopeStack: DequeUpdateScope java.util.ArrayDequeUpdateScope()Snapshot.takeSnapshot(readObserver { mutableState -//一个state 可能会被多个UpdateScope读取var updateScopes state2Scope[mutableState]if (updateScopes.isNullOrEmpty()) {updateScopes mutableSetOf()state2Scope[mutableState] updateScopes}//查看栈顶的updateScopes然后放入一个映射中.//这样我们就可以知道 state 更新了哪些 updateScopes 需要被重新重组val updateScope scopeStack.peek();if (updateScope ! null) {updateScopes.add(updateScope)}})
//略
最后我们看看如何构造这些updateScopes栈对象.
//id 标记某个composable函数方便索引
//update回调composable在数据更新的时候
data class UpdateScope(val id:Int, val update:()-Unit)
fun OneTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {// 创建scope 然后赋予一个唯一 id 方便查找.val scope UpdateScope(0x00001){//数据更新的时候OneTvComposable(composer,parent,state)}scopeStack.push(scope)//创建一个 Textview 去展示val viewId OneTextMyText(viewId, ${state.intValue}, parent)scopeStack.pop()
}其他函数类似就不演示,我们图展示下列Composable代码运行流程
OneColumnComposable(Composer, rootView) { view -OneTvComposable(Composer, view, displayOneState)TwoTvComposable(Composer, view, displayTwoState)OneBtnComposable(Composer, view, displayOneState)
}
OneColumnComposable 函数内部不会读取任何状态,所以仅仅会压入栈不会触发 snapshot 读取.图示例如下:
fun OneColumnComposable(composer: Composer,parent: ViewGroup,content: (ViewGroup) - Unit
) {// 创建scope 然后赋予一个唯一 id 方便查找.val scope UpdateScope(0x00004){//数据更新的时候OneColumnComposable(composer,parent,state)}scopeStack.push(scope)//创建一个 LinearLayoutMyColumn(oneColumn, parent, { view -content.invoke(view)})scopeStack.pop()}运行OneTvComposable,会压入一个新的 Scope,由于在这个函数读取了 state,会触发 snapshot 读取回调,更新updateScope映射信息 运行TwoTvComposable时,OneTvComposable会弹出之前的栈.会压入一个新的 Scope,由于在这个函数读取了 state,会触发snapshot 读取回调,更新updateScope映射信息 fun TwoTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {// 创建scope 然后赋予一个唯一 id 方便查找.val scope UpdateScope(0x00002){//数据更新的时候TwoTvComposable(composer,parent,state)}scopeStack.push(scope)//创建一个 Textview 去展示val viewId TwoTextMyText(viewId, ${state.intValue}, parent)scopeStack.pop()
}OneBtnComposable函数并不会读取 state 而是简单的写入.所以并不会影响 state2Scope
fun OneBtnComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {// 创建scope 然后赋予一个唯一 id 方便查找.val scope UpdateScope(0x00003){//数据更新的时候OneBtnComposable(composer,parent,state)}val viewId OneBtnMyButton(viewId, changeState, parent, {state.intValue 1})scopeStack.pop()}当OneBtnComposable函数结束的时候OneColumnComposable也对应结束了函数周期.所有 信息将会从scopestack将会弹出 现在我们有了state2Scope存储信息,在 state 更改时调用对应的UpdateScope的回调即可完成更新.
Snapshot.registerGlobalWriteObserver {//全局作用于的快照被写入的时候回调//调用通知.此时会触发registerApplyObserver回调Snapshot.sendApplyNotifications()
}
Snapshot.registerApplyObserver { anies, snapshot -for (any in anies) {//any 就是我们的 stateval updateScopes state2Scope[any]//重新调用函数触发更新updateScopes.update()}
}上面的设计方案有一个比较致命的性能问题比如我们看一下下面的代码,根布局会根据backgroundColorState修改自身背景颜色
private val backgroundColorState mutableIntStateOf(Color.BLUE)
//OneColumnComposable会读取backgroundColorState变量去设置背景色
OneColumnComposable(Composer, rootView,backgroundColorState) { view -OneTvComposable(Composer, view, displayOneState)TwoTvComposable(Composer, view, displayTwoState)//按钮会修改背景色 OneBtnComposable(Composer, view, backgroundColorState)
}
fun OneColumnComposable(composer: Composer,parent: ViewGroup,content: (ViewGroup) - Unit
) {// 创建scope 然后赋予一个唯一 id 方便查找.val scope UpdateScope(0x00004){//数据更新的时候OneColumnComposable(composer,parent,state)}scopeStack.push(scope)//创建一个 LinearLayout,并制定背景色颜色MyColumn(oneColumn, parent,backgroundColorState.value, { view -content.invoke(view)})scopeStack.pop()
}这时候触发切换颜色的时候我们期待仅有OneColumnComposable会被回调.但是实际上OneColumnComposable,OneTvComposable,TwoTvComposable,OneBtnComposable全部会重新触发.我们可以在建立一个新的树称为 Group树,这个树中每个节点存储是否Dirty,然后更新的时候选择性更新判断.
树中节点如下
data class Group(val id: Int,var parent: Group?,val child: MutableScatterMapInt, Group mutableScatterMapOf()
) {//标记节点是否需要更新var dirtyFlag: Int DIRTY_STATE_INITcompanion object {val ROOT_NODE Group(0x0000, null, mutableScatterMapOf())//节点未重组过,需要重组val DIRTY_STATE_INIT 0//节点是干净的不需要被重组val DIRTY_STATE_CLEAN DIRTY_STATE_INIT 1//节点数据过时需要重组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()}
}我们可以在创建scope栈的时候结合一起构建这个 group 树.我们举例OneTvComposable来说明.我们顺带把所有这类任务的代码放入一个叫Composer对象中
fun OneTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {//创建一个 scope 栈对象,并且创建一个 group 树节点val group composer.startGroup(0x00001)if (group.change()) {Logd(invoke OneTvComposable)val viewId OneTextMyText(viewId, ${state.intValue}, parent)} else {}//结束的时候时候我们我们弹出 scope 栈对象,并维护 group 树节点group.endScope {OneTvComposable(composer, parent, state)}
}//Composer对象内嵌函数
class Composer{//标记由于 state 写入需要重新刷新的 groupval dirtyGroup: MutableScatterMapInt, Group mutableScatterMapOfInt, Group()fun startGroup(composableId: Int): UpdateScope {//调用startGroup此 Group已经被重组,移除标记val dirtyGroup dirtyGroup.remove(composableId)//构建好 group 树节点,这个树用于判断数据变化时更新策略.提升重组性能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}//构造 scope 栈对象,方便感知刷新域val updateScope UpdateScope(composableId, group, null)scopeStack.push(updateScope)return updateScope}//弹出栈,并重新标记 group 为干净fun endScope(update: (() - Unit)) {this.update updateComposer.scopeStack.pop()group.dirtyFlag DIRTY_STATE_CLEAN}
}
最后我们在查阅下写入回调处的处理.
Snapshot.registerGlobalWriteObserver {Snapshot.sendApplyNotifications()
}
Snapshot.registerApplyObserver { anies, snapshot -for (any in anies) {val updateScopes state2Scope[any]updateScopes?.forEach { scope -dirtyGroup[scope.id] (scope.group)//仅标记被污染的 group,可以避免子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()
}
上面便是一个简易版本 View 下模拟 compose 流程.Group树用数据变时怎么样刷新,UpdateSope用于在哪刷新,而Composable描述了怎么样的一个 View 树 最后我们贴出完整相关代码
data class Group(val id: Int,var parent: Group?,val child: MutableScatterMapInt, 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 MutableScatterMapAny, MutableSetUpdateScope()val scopeStack: DequeUpdateScope java.util.ArrayDequeUpdateScope()val dirtyGroup: MutableScatterMapInt, Group mutableScatterMapOfInt, 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.findViewWithTagLinearLayout(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.findViewWithTagTextView(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.findViewWithTagButton(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) {//创建一个 scope 栈对象,并且创建一个 group 树节点val group composer.startGroup(0x00001)if (group.change()) {Logd(invoke OneTvComposable)val viewId OneTextMyText(viewId, ${state.intValue}, parent)} else {}//结束的时候时候我们我们弹出 scope 栈对象,并维护 group 树节点group.endScope {OneTvComposable(composer, parent, state)}}fun TwoTvComposable(composer: Composer, parent: ViewGroup, state: MutableIntState) {val group composer.startGroup(0x00002)if (group.change()) {val viewId TwoTextLogd(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 OneBtnLogd(invoke OneBtnComposable)MyButton(id, changeState, parent, {
// state.intValue 1state.intValue Color.RED})} else {}group.endScope {OneBtnComposable(composer, parent, state)}}}Compose 源码阅读
我们有如下Demo作为讲解说明. 一个按钮和一个文本,每次点击按钮触发数字单调递增
示例代码如下:
//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: MutableStateInt) {val value displayState.valueText(display $value)
}Composable
Preview
fun HomePreview(){Home()
}本文需要有基础的快照和SlotTable概念以避免重复造轮子.
手撸 View 下局部自动更新
MainCompose原始的函数会在编译后变为以下代码. ComposableComposableTarget(applier androidx.compose.ui.UiComposable)public static final void MainCompose(NotNull final MutableState displayState, Nullable Composer $composer, final int $changed) {//每一个 compose 都会构建一个 Group,最终Group也会组成一个树.(一定要注意这个不是渲染树 LayoutNode,Compose 里有多颗树,这颗树用做数据处理) //而startRestartGroup也会创建一个 Group 放入树中$composer $composer.startRestartGroup(-1327587884);ComposerKt.sourceInformation($composer, C(MainCompose)471341L22:MainActivity.kt#ffoge4);//结合一些数据判断当前是否可以跳过重组int $dirty $changed;if (($changed 14) 0) {$dirty | $composer.changed(displayState) ? 4 : 2;}//如果当前 Composeable是skippable那么会结合当前入参判断是否能跳过//skippable本文后面会简单介绍if (($dirty 11) 2 $composer.getSkipping()) {$composer.skipToGroupEnd();} else {if (ComposerKt.isTraceInProgress()) {ComposerKt.traceEventStart(-1327587884, $dirty, -1, com.example.composedemo.MainCompose (MainActivity.kt:45));}//如果需要重组那么进行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();}}//标记当前 Group 在树中结束,并返回一个 Compose 更新域(ScopeUpdateScope).//ScopeUpdateScope会在displayState更新时调用updateScope进而发生重组ScopeUpdateScope var5 $composer.endRestartGroup();if (var5 ! null) {var5.updateScope((Function2)(new Function2() {public final void invoke(Composer $composer, int $force) {//如果数据变更会会回调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;}}));}}你会惊讶的发现函数背后做的事情和我们自己实现在 View 下差不多. 我们这里额外补充一个细节,你会注意到有一个$composer.getSkipping()函数才会判断当前 Composeable 是否会跳过,否则一定会触发重组.
那么时候函数getSkipping才为 true 呢? Compose 编译器会为每个Composable做一个标记.如果利用可以利用入参和之前传入参数判断相等那么可以被标记skippable.
我们比较下下面的两个函数是否都可以被标记skippable?
//可以被标记skippable,因为displayState数值可以取出来和之前的比较
Composable
fun MainCompose(displayState: MutableStateInt) {val value displayState.valueText(display $value)
}//不可以被标记skippable,因为list的实例可以比较,但是内部的内容和顺序不可推断
Composable
fun MainCompose2(list: MutableListString) {Text(display $${list.joinToString { it }})
}相关具体知识点建议阅读 what-do-the-stable-and-immutable-annotations-mean-in-jetpack-compose
有相关工具可以打印出编译视角下函数结构,这里直接给出结果:
//标记skippable
restartable skippable scheme([androidx.compose.ui.UiComposable]) fun MainCompose(stable displayState: MutableStateInt
)
//不标记skippable,这个函数被重组的时候一定会重新触发.
restartable scheme([androidx.compose.ui.UiComposable]) fun MainCompose2(unstable list: MutableListString
)我们看下 MainCompose2被编译后的代码是不会存在skipToGroupEnd函数的调用.重组时直接触发不存在跳过逻辑.
Composable
fun MainCompose2(list: MutableListString) {Text(display $${list.joinToString { it }})
}ComposableComposableTarget(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)511428L44: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是如何实现,
//Composer.ktclass ComposerImpl(ComposeCompilerApioverride fun startRestartGroup(key: Int): Composer {//创造一个 Group 树节点,由于这块比较复杂不展开细说start(key, null, GroupKind.Group, null)//创建一个重组域addRecomposeScope()return this}//创建一个重组域放入栈中private fun addRecomposeScope() {//...略val scope RecomposeScopeImpl(composition as CompositionImpl)invalidateStack.push(scope)//...略}ComposeCompilerApioverride fun endRestartGroup(): ScopeUpdateScope? {//...略//弹出栈val scope if (invalidateStack.isNotEmpty()) invalidateStack.pop()//...略}}
我们最后官方源码中,入口快照 take函数调用处如下所示 //ReComposer.ktclass Recomposer{private inline fun T composing(composition: ControlledComposition,modifiedValues: IdentityArraySetAny?,block: () - T): T {val snapshot Snapshot.takeMutableSnapshot(readObserverOf(composition), writeObserverOf(composition, modifiedValues))try {return snapshot.enter(block)} finally {applyAndCheck(snapshot)}}
}
我们首先看readObserverOf实现
//Composition.kt
//以 state 为 key,RecomposeScopeImpl为 value
//value内部还有一层List封装,因为 state 可以映射多个RecomposeScopeImpl
private val observations ScopeMapRecomposeScopeImpl()override fun recordReadOf(value: Any) {//value 就是 state 对象//currentRecomposeScope就是更新域composer.currentRecomposeScope?.let {//存储state 和RecomposeScopeImpl关系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 ScopeMapT : Any {val map mutableScatterMapOfAny, Any()val size get() map.size//内部会构建 Set 集合放入多个 value 去对应一个 keyfun add(key: Any, scope: T) {map.compute(key) { _, value -when (value) {null - scopeis MutableScatterSet* - {Suppress(UNCHECKED_CAST)(value as MutableScatterSetT).add(scope)value}else - {if (value ! scope) {val set MutableScatterSetT()Suppress(UNCHECKED_CAST)set.add(value as T)set.add(scope)set} else {value}}}}}
}我们知道快照有两个作用域一个全局的和 snapshot.enter后绑定的. 而我们业务中往往在全局作用域去写入state,所以本文我们先不阅读writeObserverOf代码.(如果对快照概念模糊建议阅读参考文献) 在 Compose全局写入观察位于如下代码中:
//Recomposer.kt
private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) - Unit
) {//...Snapshot.registerApplyObserver { changed, _ -//这里 Lamba最后的deriveStateLocked会返回一个协程的Continuation//Continuation.resume 调用会恢对应协程继续运行synchronized(stateLock) {if (_state.value State.Idle) {changed.fastForEach {//it 是 state 对象//将所有被修改 state 放入集合中snapshotInvalidations.add(it)}//最后通知某个协程函数,去触发重组deriveStateLocked()} else null}?.resume(Unit)}//...
}private var workContinuation: CancellableContinuationUnit? null
private fun deriveStateLocked(): CancellableContinuationUnit? {return if (newState State.PendingWork) {//这里高阶函数的作用是先workContinuation返回,再将workContinuation设置为 nullworkContinuation.also {workContinuation null}} else null
}我们通过上面的分析workContinuation赋值点就是就是Compose开始重组核心点
//Composer.kt
suspend fun runRecomposeAndApplyChanges() recompositionRunner { parentFrameClock -//..略//为简化流程shouldKeepRecomposing可以视为永远为 truewhile (shouldKeepRecomposing) {//判断当前是否dirty 的 scope,如果没有那么将当前协程挂起,并将continuation 赋值给workContinuation//可以简单判断snapshotInvalidations为空就执行挂起awaitWorkAvailable()//等候下一个 VSYNC 回调执行实际重组.parentFrameClock.withFrameNanos { frameTime -//这里会取出 dirty 的scope 开始进行重组工作//...略//toRecompose是一个CompositionImpl集合.//Mainwhile (toRecompose.isNotEmpty() || toInsert.isNotEmpty()) {try {toRecompose.fastForEach { composition -alreadyComposed.add(composition)//最终会取出对应 scope回调 递归回调函数performRecompose(composition, modifiedValues)?.let {toApply it}}} catch (e: Exception) {processCompositionError(e, recoverable true)clearRecompositionState()returnwithFrameNanos} finally {toRecompose.clear()}}}}我们最后看看awaitWorkAvailable相关代码
//Recomposer.ktprivate val hasSchedulingWork: Booleanget() synchronized(stateLock) {//是否有 dirty 的 scopesnapshotInvalidations.isNotEmpty() ||compositionInvalidations.isNotEmpty() ||hasBroadcastFrameClockAwaitersLocked}private suspend fun awaitWorkAvailable() {if (!hasSchedulingWork) {suspendCancellableCoroutineUnit { co -synchronized(stateLock) {//如果有 dirty 的数据那么直接恢复协程完成重组if (hasSchedulingWork) {co} else {//挂起协程workContinuation conull}}?.resume(Unit)}}
}参考
一文看懂 Jetpack Compose 快照系统 探索 Jetpack Compose 内核深入 SlotTable 系统 what-do-the-stable-and-immutable-annotations-mean-in-jetpack-compose 文章转载自: http://www.morning.qwnqt.cn.gov.cn.qwnqt.cn http://www.morning.zdxss.cn.gov.cn.zdxss.cn http://www.morning.srhqm.cn.gov.cn.srhqm.cn http://www.morning.tcpnp.cn.gov.cn.tcpnp.cn http://www.morning.jrtjc.cn.gov.cn.jrtjc.cn http://www.morning.mkfhx.cn.gov.cn.mkfhx.cn http://www.morning.ltrms.cn.gov.cn.ltrms.cn http://www.morning.demoux.com.gov.cn.demoux.com http://www.morning.sgrdp.cn.gov.cn.sgrdp.cn http://www.morning.xgjhy.cn.gov.cn.xgjhy.cn http://www.morning.psxfg.cn.gov.cn.psxfg.cn http://www.morning.prls.cn.gov.cn.prls.cn http://www.morning.jfnlj.cn.gov.cn.jfnlj.cn http://www.morning.slysg.cn.gov.cn.slysg.cn http://www.morning.bfkrf.cn.gov.cn.bfkrf.cn http://www.morning.xgxbr.cn.gov.cn.xgxbr.cn http://www.morning.dmzfz.cn.gov.cn.dmzfz.cn http://www.morning.rqdx.cn.gov.cn.rqdx.cn http://www.morning.fxxmj.cn.gov.cn.fxxmj.cn http://www.morning.kmqlf.cn.gov.cn.kmqlf.cn http://www.morning.lfcfn.cn.gov.cn.lfcfn.cn http://www.morning.kwqwp.cn.gov.cn.kwqwp.cn http://www.morning.nbpqx.cn.gov.cn.nbpqx.cn http://www.morning.mnbcj.cn.gov.cn.mnbcj.cn http://www.morning.bfcrp.cn.gov.cn.bfcrp.cn http://www.morning.hxsdh.cn.gov.cn.hxsdh.cn http://www.morning.qkrgk.cn.gov.cn.qkrgk.cn http://www.morning.dgxrz.cn.gov.cn.dgxrz.cn http://www.morning.mkfr.cn.gov.cn.mkfr.cn http://www.morning.itvsee.com.gov.cn.itvsee.com http://www.morning.xdhcr.cn.gov.cn.xdhcr.cn http://www.morning.btwlp.cn.gov.cn.btwlp.cn http://www.morning.wsxxq.cn.gov.cn.wsxxq.cn http://www.morning.gfnsh.cn.gov.cn.gfnsh.cn http://www.morning.nfpct.cn.gov.cn.nfpct.cn http://www.morning.spfq.cn.gov.cn.spfq.cn http://www.morning.bkylg.cn.gov.cn.bkylg.cn http://www.morning.mlwpr.cn.gov.cn.mlwpr.cn http://www.morning.tqklh.cn.gov.cn.tqklh.cn http://www.morning.htbbp.cn.gov.cn.htbbp.cn http://www.morning.nshhf.cn.gov.cn.nshhf.cn http://www.morning.jpkk.cn.gov.cn.jpkk.cn http://www.morning.rlxnc.cn.gov.cn.rlxnc.cn http://www.morning.mftdq.cn.gov.cn.mftdq.cn http://www.morning.cpktd.cn.gov.cn.cpktd.cn http://www.morning.frllr.cn.gov.cn.frllr.cn http://www.morning.nccyc.cn.gov.cn.nccyc.cn http://www.morning.jzkqg.cn.gov.cn.jzkqg.cn http://www.morning.mztyh.cn.gov.cn.mztyh.cn http://www.morning.rxyz.cn.gov.cn.rxyz.cn http://www.morning.plcyq.cn.gov.cn.plcyq.cn http://www.morning.wfyqn.cn.gov.cn.wfyqn.cn http://www.morning.dlurfdo.cn.gov.cn.dlurfdo.cn http://www.morning.kmlmf.cn.gov.cn.kmlmf.cn http://www.morning.jiuyungps.com.gov.cn.jiuyungps.com http://www.morning.zxwqt.cn.gov.cn.zxwqt.cn http://www.morning.hnrqn.cn.gov.cn.hnrqn.cn http://www.morning.ksggr.cn.gov.cn.ksggr.cn http://www.morning.lmbm.cn.gov.cn.lmbm.cn http://www.morning.qfnrx.cn.gov.cn.qfnrx.cn http://www.morning.mywmb.cn.gov.cn.mywmb.cn http://www.morning.lmyq.cn.gov.cn.lmyq.cn http://www.morning.pswqx.cn.gov.cn.pswqx.cn http://www.morning.wpmlp.cn.gov.cn.wpmlp.cn http://www.morning.ryxbz.cn.gov.cn.ryxbz.cn http://www.morning.taipinghl.cn.gov.cn.taipinghl.cn http://www.morning.qrlsy.cn.gov.cn.qrlsy.cn http://www.morning.ztqyj.cn.gov.cn.ztqyj.cn http://www.morning.mmqhq.cn.gov.cn.mmqhq.cn http://www.morning.ykwbx.cn.gov.cn.ykwbx.cn http://www.morning.khpx.cn.gov.cn.khpx.cn http://www.morning.nwynx.cn.gov.cn.nwynx.cn http://www.morning.ybgyz.cn.gov.cn.ybgyz.cn http://www.morning.nzdks.cn.gov.cn.nzdks.cn http://www.morning.ychrn.cn.gov.cn.ychrn.cn http://www.morning.cpnsh.cn.gov.cn.cpnsh.cn http://www.morning.pfjbn.cn.gov.cn.pfjbn.cn http://www.morning.wztlr.cn.gov.cn.wztlr.cn http://www.morning.wztnh.cn.gov.cn.wztnh.cn http://www.morning.wglhz.cn.gov.cn.wglhz.cn