隐藏的性能风险
首先来看下面这段代码
var name by mutableStateOf("xiaomi")
setContent {
Log.v("wuyue","1111")
Column {
Log.v("wuyue","2222")
Text(text = name, Modifier.clickable {
Log.v("wuyue","click")
name = "huawei"
})
}
Log.v("wuyue","3333")
}
这个界面第一次渲染的时候,执行的顺序很容易理解:
当你点击了以后:
看下log日志 ,你会发现 改变的不仅仅是Text这个view 被Recompose,而且与他平级的 函数调用也执行了一次
有人会问了, Text为啥和那2个log语句是平级的? 中间不还是隔着一个Column吗?
看下源码我们可以知道 这个Column是个 内联函数,也就是说 真正当kotlin编译完成以后 ,就不存在Column 这个函数了,他是把这个Column 里面包含的那些语句 直接放到外部。(不明白的可以看下Kotlin的 inline函数)
有人就会觉得 这个特性 是不是会导致整个Compose的渲染效率低呢,毕竟 这种 “牵一发动前身” 的特性比较容易埋坑呀
我们再看一个例子
setContent {
Log.v("wuyue","1111")
Column {
Log.v("wuyue","2222")
Text(text = name, Modifier.clickable {
Log.v("wuyue","click")
name = "huawei"
})
Complex()
}
Log.v("wuyue","3333")
}
@Composable
fun Complex(){
Log.v("wuyue","5555")
}
当你click的时候:
你会发现 我的Complex的view 并没有执行555,为啥?
因为Compose做了优化,他只有发现你的compose组件绑定的数据有了变化 才会协同更新,如果你的compose 组件没有发生数据变化,则不会更新。
Recompose中的相等性判断
前面我们提到过 在重组的过程中Compose组件绑定的数据有了变化 才会更新,那么如何理解绑定的数据有了变化? Compose是认为 引用发生了变化就变化 还是用equals 方法来判定的?
@Composable
fun Complex(s: Student) {
Log.v("wuyue","Recompose")
Text(text = s.name)
}
data class Student(var name: String = "")
注意看下面的代码:
var name by mutableStateOf("xiaomi")
var student = Student("xxx")
setContent {
Column {
Text(text = name, Modifier.clickable {
name = "huawei"
student = Student("xxx")
})
Complex(student)
}
}
看下执行结果:
显然,当我们更换了 引用以后,对应的组件也会发生recompose,注意了啊,我们这里的student并没有定义成mutableStateOf 也就是说Complex 这个地方的Recompose 是因为Text组件发生了变化,我这里的Complex被动的发生了变化,这里一定要理解清楚
到这里似乎有人会猜想: 嗯,Recompose 肯定是 根据引用的变化来决定是否要跟随刷新ui的。 我们可以继续做实验看看
继续改一下代码,这次我们只改对象里的field的值,不去改索引看看。
var name by mutableStateOf("xiaomi")
var student = Student("xxx")
setContent {
Column {
Text(text = name, Modifier.clickable {
name = "huawei"
student.name="123"
Log.v("wuyue","click")
})
}
Complex(student)
}
看下结果
这个就很奇怪了,这一次我们的实验过程是 直接改name 不改引用 但是组件也跟着刷新了,这是咋回事?
这好像不太符合我们以往的认知呀,在java的世界中 对于相等性的判断 往往要么是根据equals 要么根据 引用是否相等, 就好比kotlin中 ==代表是equals 而=== 是代表引用
而在这里 Compose 好像两者都支持?
我们再接着做一个实验
var name by mutableStateOf("xiaomi")
var student = Student("xxx")
setContent {
Column {
Text(text = name, Modifier.clickable {
name = "huawei"
student = Student("xxx")
Log.v("wuyue","click")
})
}
Complex(student)
}
@Composable
fun Complex(s: Student) {
Log.v("wuyue","Recompose")
Text(text = s.name)
}
data class Student(val name: String = "")
注意了这次我们的data class的name 不再是var了,而是val,
奇怪?为啥写法明明和第一个一摸一样,就是一个var变成了val,这次就没有Recompose了
而且甚至于:
我下面这样写: 注意下面的代码 我点击事件 只改了name 我甚至都没有去改student
var name by mutableStateOf("xiaomi")
var student = Student("xxx")
setContent {
Column {
Text(text = name, Modifier.clickable {
name = "huawei"
Log.v("wuyue","click")
})
}
Complex(student)
}
@Composable
fun Complex(s: Student) {
Log.v("wuyue","Recompose")
Text(text = s.name)
}
data class Student(var name: String = "")
强调下,上面的代码是var 定义的 name,并且click点击事件里面没有更改student,结果因为name触发了recompose,我的Complex 也触发了recompose
是不是很迷糊?
其实这里很简单啊,大家谨记这个原则,
对于被动被Recompose的组件来说,Compose决定他是否会被真正的Recompose很大程度实际上是取决于equals的相等性判断(data class 会自动帮你根据field来重写equals), 也就是说 equals不等则一定会被动触发真正的recompose
如果你重写了equals函数,例如kotlin的data class这种场景
当引用发生了变化,而field没有发生变化的时候 recompose是否会被动触发 则取决于 你这个类的field是不是val,如果是val 则不会被动触发,是var则会被动触发,为什么?
因为如果是var 则代表这个field将来可能再某个时候被修改值啊,最终也会导致field的值发生变化的,而如果是val,则代表你这个值已经定死了,不会发生改变了,那当然不会被recompose。
谁来保证未来相等?
前面我们做了许多实验 来验证 被动Recompose的 触发条件。 可能会有人觉得你这个被动recompose的触发条件挺容易满足的啊,这样会不会导致 性能有一些降低呢? 其实是可能的,但是这种情况往往也可以避免,你只要真正弄明白Recompose 即可。比如 针对上述的场景
@Stable
class Student(var name: String = "")
我们可以使用Stable注解 来保证即使是var 也不会被 被动Recompose
有人可能觉得 这个很好用啊,但是要注意了 我们看下他的注释:
用了这个注解 会导致什么问题? 会导致你的属性发生了变化,界面不会更新呀。
譬如下面这段代码:
var name by mutableStateOf("xiaomi")
var student = Student("xxx")
setContent {
Column {
Text(text = name, Modifier.clickable {
name = "huawei"
student.name="123"
Log.v("wuyue","click")
})
Complex(student)
}
}
@Composable
fun Complex(s: Student) {
Log.v("wuyue","Recompose")
Text(text = s.name)
}
首先是name这个mutableState来发起主动更新,主动更新的时候 stutent的name也变了,但是这个时候你会发现Complex没有更新到, 怎么解决这个问题?
简单啊,我们也让他主动更新即可
@Stable
class Student( name: String = ""){
var name by mutableStateOf(name)
}
关于这个Stable注解再多说两句,这个注解只是告诉Compose 我这个对象 现在相等以后也相等,现在不等以后也不等, 注意这个是告诉, 而不是保证,保证这个对象稳定是让程序员来保证的。 这里很像HashMap中相等性的判断,大家可以对照了理解。
另外Stable注解 一般都不会和data class 配合使用,当然你在data class里面 再实现equals的话就另说了 (果然equals和hashcode 是所有java程序员的痛)
或者你说上面的我都搞不懂 太烦了,有没有一劳永逸的原则,有的
- 所有的var的属性 都使用mutableStateOf
- 不要重写equals方法
- 加个stable 注解