前言
我去年發(fā)現(xiàn)Android新出了個(gè)UI框架Compose,看了Demo后發(fā)現(xiàn),納尼!和Flutter太像了吧,無(wú)論是編程邏輯、控件名,都有很多相似的地方。谷歌自己抄自己?一直想去嘗試,感受一下,直到今年才有時(shí)間去寫了小項(xiàng)目。
思考
我在寫代碼的時(shí)候和Flutter去做對(duì)比,就想到它倆的UI刷新機(jī)制是否有相似點(diǎn),如果不一樣,那Compose的刷新機(jī)制是什么?
在看Compose的刷新機(jī)制前,我回憶了一下Flutter的刷新機(jī)制。
簡(jiǎn)單講Flutter是通過(guò)調(diào)用StatefulWidget的setState方法,重新走一遍build方法中的代碼。那原理就是setState調(diào)用時(shí)會(huì)自己添加到BuildOwner的dirtyElements臟鏈表中,然后調(diào)用window.scheduleFrame來(lái)注冊(cè)Vsync回調(diào),當(dāng)下一次vsync信號(hào)的到來(lái)時(shí)會(huì)重新繪制UI。
所以我覺得Flutter更像是一個(gè)屏幕,調(diào)用setState方法不斷重新構(gòu)建UI頁(yè)面,一幀一幀的。那Compose是不是也是這樣呢?
嘗試
Demo如下,在頁(yè)面上顯示一個(gè)Text控件,和一個(gè)按鈕,每一次點(diǎn)擊,Text顯示的數(shù)字自增。
@Composable
fun demo() {
// 關(guān)鍵代碼
var versionCode by remember { mutableStateOf(0) }
Column {
Button(
onClick = { versionCode++ }) {
Text("Add +")
}
Text( versionCode.toString() )
}
}
UI發(fā)生變化的關(guān)鍵代碼就是 by remember { mutableStateOf(0) } ,這行代碼刪除后,怎么按UI都不會(huì)變化。我發(fā)每次versionCode變化的時(shí)候會(huì)重新走一遍demo()內(nèi)的代碼,這時(shí)該類中有其他方法,其他方法代碼并不會(huì)重走。如果把Text中的versionCode引用刪除,寫一個(gè)固定值,這個(gè)再點(diǎn)擊按鈕,也不會(huì)重新走一遍代碼。
Compose 是如何進(jìn)行更新的?如何做到更新時(shí)只有引用的方法內(nèi)刷新?不引用為何不刷新方法?
分析
帶著以上三個(gè)問(wèn)題,點(diǎn)進(jìn)mutableStateOf中去,看一看源碼。

進(jìn)入 createSnapshotMutableState

進(jìn)入 ParcelableSnapshotMutableState

進(jìn)入SnapshotMutableStateImpl,下面這個(gè)類中,紅框標(biāo)記的是關(guān)鍵代碼,這個(gè)方法的注解是
A single value holder whose reads and writes are observed by Compose.Additionally, writes to it are transacted as part of the [Snapshot] system.
我理解的意思是,對(duì)value這個(gè)值做了監(jiān)聽,只要是 Compose的UI 引用了value,當(dāng)其發(fā)生變化時(shí)就能自動(dòng)更新

這里的 value 就是給 versionCode 賦的值,這里的 get() 方法中會(huì)調(diào)用 readable() ,把當(dāng)前 state 保存起來(lái)(我認(rèn)為這里的 state 可以理解為 Flutter 的 state)。set()方法內(nèi)會(huì)進(jìn)行對(duì)比,當(dāng)兩個(gè)對(duì)象的地址不一致時(shí),會(huì)觸發(fā)監(jiān)聽通知,重寫用的Compose方法,
這也就解釋了之前說(shuō)的三個(gè)問(wèn)題。
- Compose的刷新本質(zhì)是對(duì) value 的監(jiān)聽通知;
- 為了避免過(guò)度刷新,將刷新范圍固定到最小的標(biāo)記@Compose的注解的方法內(nèi),包括方法內(nèi)的其他方法(和Flutter的StatefulWidget的刷新范圍一樣),為提高性能,會(huì)把頻繁刷新的View(Flutter中的widget)封裝為單獨(dú)的Compose方法(Stful);
- 對(duì)value的引用,調(diào)用了get內(nèi)的注冊(cè)方法,不引用,也就不會(huì)引起刷新。
以上就是我對(duì)Compose的刷新機(jī)制的理解,
應(yīng)用
了解了Compose的刷新機(jī)制后,怎么才能高效的使用這中邏輯編程呢?我在踩了一堆坑后,有以下幾個(gè)寫代碼的注意點(diǎn),僅供參考。
一、
如果在方法內(nèi)使用mutableStateOf,需要包一層remember函數(shù),它的作用是運(yùn)行完里面的代碼后,就會(huì)存在緩存里,再次執(zhí)行這行代碼,不會(huì)再次初始化,會(huì)從緩存中拿出 State的對(duì)象,防止多次初始化。 如果你偏不,可以試試看有什么神奇的現(xiàn)象??。’ (PS :在remember函數(shù)中有個(gè)熟悉的參數(shù) Key,熟悉Flutter的估計(jì)都明白干啥的了,這就不再啰嗦了)
// 成員變量可以不用套,因?yàn)楸旧砭统跏蓟淮?var versionCode by mutableStateOf(0)
@Composable
fun demo() {
// 方法內(nèi)這么寫
var versionCode by remember { mutableStateOf(0) }
Column {
Button(
onClick = { versionCode++ }) {
Text("Add +")
}
Text( versionCode.toString() )
}
}
二、
對(duì)value的刷新是設(shè)置新的值,是對(duì)比對(duì)象本身,如果只改變了對(duì)象內(nèi)的值,是不會(huì)放生刷新的。如代碼中A對(duì)象并沒(méi)有改變。
data class A(var a: Int = 0)
@Composable
fun demo() {
val versionCode by remember { mutableStateOf(A(0)) }
Column {
Button(
onClick = { versionCode.a++ }) {
Text("Add +")
}
Text( versionCode.toString() )
}
}
遇到這種情況需要刷新有兩種辦法
- 復(fù)制對(duì)象,改變對(duì)象地址
Button(
onClick = { versionCode.copy(a = versionCode.a++) }) {
Text("Add +")
}
- 給對(duì)象內(nèi)的變量實(shí)現(xiàn)mutableStateOf
data class A(var a: MutableState<Int> = mutableStateOf(0)) {
// 或?qū)懺谙旅嬗?by 代理的方式
var a by mutableStateOf(0)
}
三、
單個(gè)對(duì)象好復(fù)制,好改,遇到列表,數(shù)組類的對(duì)象刷新,該怎么做呢,換列表對(duì)象?不合適。好在官方已經(jīng)替咱們想到了這一點(diǎn)。神器 mutableStateListOf,當(dāng)列表內(nèi)的數(shù)據(jù)發(fā)生變化時(shí),會(huì)刷新UI
val data = mutableStateListOf(1, 2, 3)
@Composable
fun demo() {
Column {
Button(onClick = {
data.add(data.last() + 1)
}) {
Text(text = "onclick")
}
for (d in data) {
Text(text = "data:$u0z1t8os")
}
}
}
我大概遇到的UI刷新上的坑,可以歸為以上三種,還有其他的歡迎交流補(bǔ)充。
總結(jié)
Compose的刷新機(jī)制簡(jiǎn)單來(lái)講是有范圍,有組織的用觀察者模式進(jìn)行刷新。 分析了個(gè)大概,更深層次的原理,以后慢慢啃。 Compose的編程模式和Flutter很像,會(huì)flutter很容易上手,還挺有意思的。
如果有錯(cuò)誤,請(qǐng)留言指正
本文轉(zhuǎn)自 https://juejin.cn/post/7080453608231141412,如有侵權(quán),請(qǐng)聯(lián)系刪除。