Android Jetpack Compose基础之生命周期-重组
Compose是通过重组刷新UI,Compose发生重组时,只有状态发生更新的Composable才会参与重组,没有变化的Compsable会跳过本次重组。
首先我们简单看下Compsable的生命周期
Composable生命周期简介
从官方文档我们可以知道,Compsable生命周期分为:
1、添加到视图树:将Composable添加到组合中,首次执行,在视图树上新增节点
2、重组:不断重组,更新视图树的界面
3、从视图树移除:将Composable从组合中移除
重组
Composable如何确定重组的最小范围?
示例
让我们先看如下代码的日志输出
kotlin
@Composable
fun stateChangeComposable() {
Log.i("stateChangeComposable", "stateChangeComposable run1")
var num by remember {
mutableStateOf(1)
}
Column {
Log.i("stateChangeComposable", "stateChangeComposable run2")
Button(onClick = {
Log.i("stateChangeComposable", "stateChangeComposable run click")
num++
}) {
Log.i("stateChangeComposable", "stateChangeComposable run3")
Text(text = "change")
}
Log.i("stateChangeComposable", "stateChangeComposable run4")
Text(text = "current num = $num")
}
}
界面中有个按钮,点击后num加1,一个文本展示num,点击Button后的日志输出顺序是什么样的呢?
实际输出内容:
为什么结果是这样呢?其底层原理?
因为Compse经过变异后,Compsable代码在对state读取的同时会自动建立关联,在运行过程中state发生改变时,Compse会将关联的代码块标记为Invalid,Compose会发重组并执行被标记为Invalid代码块。
只有非inline且无返回值的Compsable函数或者lambda才能够被标记为Invalid(1、因为inline函数在编译期会在调用处展开,所以他会共享调用方的重组范围;2、有返回值的函数,它的返回值会影响调用方,所以必须与调用方一起重组)
日志顺序分析
1、stateChangeComposable run click点击了自然会被打印
2、stateChangeComposable run2和stateChangeComposable run4,因为当前读取了num状态并传入了Text所以打印了
3、stateChangeComposable run3没有打印是因为没有内部没有依赖num状态,所以跳过了重组
4、而stateChangeComposable run1为什么会被打印呢,因为它是inline声明的函数,它内部的content会在调用处展开,所以它与调用方共享重组;Column源码如下
kotlin
@Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {
val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
Layout(
content = { ColumnScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
如果我们将Column替换为非内联函数Card
其打印内容为下图,发现stateChangeComposable run1未出现了
stateChangeComposable内部增加如下代码点击button后的打印结果
kotlin
@Composable
fun stateChangeComposable() {
Log.i("stateChangeComposable", "stateChangeComposable run1")
var num by remember {
mutableStateOf(1)
}
{
Log.i("stateChangeComposable", "stateChangeComposable run2")
Button(onClick = {
Log.i("stateChangeComposable", "stateChangeComposable run click")
num++
}) {
Log.i("stateChangeComposable", "stateChangeComposable run3")
Text(text = "change")
}
Log.i("stateChangeComposable", "stateChangeComposable run4")
Text(text = "current num = $num")
stateChangeComposable2 {
Log.i("stateChangeComposable", "stateChangeComposable stateChangeComposable2 click")
}
stateChangeComposable3(num) {
Log.i("stateChangeComposable", "stateChangeComposable stateChangeComposable3 click")
}
}
}
@Composable
fun stateChangeComposable2(click: () -> Unit) {
Log.i("stateChangeComposable", "stateChangeComposable stateChangeComposable2 run")
Text(text = "stateChangeComposable2",
modifier = Modifier.clickable { click() }
)
}
@Composable
fun stateChangeComposable3(num: Int, click: () -> Unit) {
Log.i("stateChangeComposable", "stateChangeComposable stateChangeComposable3 run")
Text(text = "stateChangeComposable3 getNum = $num",
modifier = Modifier.clickable { click() }
)
}
其日志输出结果如下
小结
原则:其重组依旧遵循范围最小化原则:只有收到State变化影响的代码块才会参与到重组,不依赖State的代码或参数未发生改变时则不参与本次重组。
由谁来检测参数是否发生改变 :重组过程中Composable只有其参数发生变化时,才会参与到本次重组,而Composable的参数比较由编译后传入Composer对象完成的,而Composer又是和谁去比较的呢?
简单的说 :它是从视图树上寻找对应位置的节点并与之进行比较,如果节点未发生变化,则不更新
详细的说:Composable执行过程,先将生成的Composition状态存入SlotTable,然后基于SlotTable生成LayoutNode树,并完成最终界面渲染,Composable的比较逻辑最终发生在SlotTable中
Composable中的参数是如何实现比较是否相同的
什么是稳定类型和可变类型
我们知道Composable是基于参数的比较结果来决定是否重组的,只有当参与比较的参数对象是不可变的稳定的且equals返回true,才认为是未发生改变是相等的。
不可变稳定类型
在kotlin中如基本类型、String类型、函数类型(Lambda),它们都是不可变类型,所以它们的值可信的,否则时可变类型
补充:MutableState被认为是稳定类型,虽然它的值是可变的,但因为它的value的变化是可以被记录追踪并触发重组的(等同于在新重组发生之前保持不变)
kotlin
@Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
常见可变类型
1、对象中有var修饰的成员变量
kotlin
class Person(
//因为他有一个var类型的成员变量age,所以它的equals的结果是不可信的
var age:Int
)
2、interface或者List集合也是可变类型
@Stable:将可变类型设置为稳定类型
如果我们确保可变类型在运行中是不会改变的,我们可以为其添加@Stable注解,编译期将会视为稳定类型,被添加注解的普通夫类、密封类、接口等,其子类也会被视为稳定的,可以实现避免不必要的重组