Jetpack Compose 是一個適用于 Android 的新式聲明性界面工具包稽坤。Compose 提供聲明性 API,可在不以命令方式改變前端視圖的情況下呈現(xiàn)應用界面赫编,從而使編寫和維護應用界面變得更加容易夏志。
聲明性編程范式
長期以來垒拢,Android 視圖層次結構一直可以表示為界面 widget 樹。由于應用的狀態(tài)會因用戶交互等因素而發(fā)生變化荣恐,因此界面層次結構需要進行更新以顯示當前數(shù)據(jù)液斜。最常見的界面更新方式是使用?findViewById()?等函數(shù)遍歷樹,并通過調用?button.setText(String)叠穆、container.addChild(View)?或?img.setImageBitmap(Bitmap)?等方法更改節(jié)點少漆。這些方法會改變 widget 的內部狀態(tài)。
手動操縱視圖會提高出錯的可能性痹束。如果一條數(shù)據(jù)在多個位置呈現(xiàn)检疫,很容易忘記更新顯示它的某個視圖。此外祷嘶,當兩項更新以出人意料的方式發(fā)生沖突時屎媳,也很容易造成異常狀態(tài)夺溢。例如,某項更新可能會嘗試設置剛剛從界面中移除的節(jié)點的值烛谊。一般來說风响,軟件維護的復雜性會隨著需要更新的視圖數(shù)量而增長。
在過去的幾年中丹禀,整個行業(yè)已開始轉向聲明性界面模型状勤,該模型大大簡化了與構建和更新界面關聯(lián)的工程任務。該技術的工作原理是在概念上從頭開始重新生成整個屏幕双泪,然后僅執(zhí)行必要的更改持搜。此方法可避免手動更新有狀態(tài)視圖層次結構的復雜性。
重新生成整個屏幕所面臨的一個難題是焙矛,在時間葫盼、計算能力和電池用量方面可能成本高昂。為了減少在這方面耗費的資源村斟,Compose 會智能地選擇在任何給定時間需要重新繪制界面的哪些部分贫导。
簡單的可組合函數(shù)
使用 Compose,可以通過定義一組接受數(shù)據(jù)而發(fā)出界面元素的可組合函數(shù)來構建界面蟆盹。一個簡單的示例:
關于此函數(shù)孩灯,有幾點值得注意:
此函數(shù)帶有?@Composable?注釋。所有可組合函數(shù)都必須帶有此注釋逾滥;此注釋可告知 Compose 編譯器:此函數(shù)旨在將數(shù)據(jù)轉換為界面峰档。
此函數(shù)接受數(shù)據(jù)∠痪啵可組合函數(shù)可以接受一些參數(shù)面哥,這些參數(shù)可讓應用邏輯描述界面。在本例中毅待,可組合函數(shù)接受一個?String,因此它可以按名稱問候用戶归榕。
此函數(shù)可以在界面中顯示文本尸红。為此,它會調用?Text()?可組合函數(shù)刹泄,該函數(shù)實際上會創(chuàng)建文本界面元素外里。可組合函數(shù)通過調用其他可組合函數(shù)來發(fā)出界面層次結構特石。
此函數(shù)不會返回任何內容盅蝗。發(fā)出界面的 Compose 函數(shù)不需要返回任何內容,因為它們描述所需的屏幕狀態(tài)姆蘸,而不是構造界面 widget墩莫。
此函數(shù)快速芙委、冪等且沒有附帶效應。
使用同一參數(shù)多次調用此函數(shù)時狂秦,它的行為方式相同灌侣,并且它不使用其他值,如全局變量或對?random()?的調用裂问。
此函數(shù)描述界面而沒有任何副作用侧啼,如修改屬性或全局變量。
一般來說堪簿,所有可組合函數(shù)都應使用這些屬性來編寫痊乾。
聲明性范式轉變
在許多面向對象的命令式界面工具包中,可以通過實例化 widget 樹來初始化界面椭更,通常通過膨脹 XML 布局文件來實現(xiàn)此目的符喝。每個 widget 都維護自己的內部狀態(tài),并且提供 getter 和 setter 方法甜孤,允許應用邏輯與 widget 進行交互协饲。
在 Compose 的聲明性方法中,widget 相對無狀態(tài)缴川,并且不提供 setter 或 getter 函數(shù)茉稠。實際上,widget 不會以對象形式提供把夸。通過調用帶有不同參數(shù)的同一可組合函數(shù)來更新界面而线。這使得向架構模式(如?ViewModel)提供狀態(tài)變得很容易。然后恋日,可組合項負責在每次可觀察數(shù)據(jù)更新時將當前應用狀態(tài)轉換為界面膀篮。
應用邏輯為頂級可組合函數(shù)提供數(shù)據(jù)。該函數(shù)通過調用其他可組合函數(shù)來使用這些數(shù)據(jù)描述界面岂膳,將適當?shù)臄?shù)據(jù)傳遞給這些可組合函數(shù)誓竿,并沿層次結構向下傳遞數(shù)據(jù)。
當用戶與界面交互時谈截,界面會發(fā)起?onClick?等事件筷屡。這些事件應通知應用邏輯,應用邏輯隨后可以改變應用的狀態(tài)簸喂。當狀態(tài)發(fā)生變化時毙死,系統(tǒng)會使用新數(shù)據(jù)再次調用可組合函數(shù)。這會導致重新繪制界面元素喻鳄,此過程稱為“重組”扼倘。
用戶與界面元素進行了交互,導致觸發(fā)一個事件除呵。應用邏輯響應該事件再菊,然后系統(tǒng)根據(jù)需要使用新參數(shù)自動再次調用可組合函數(shù)爪喘。
動態(tài)內容
由于可組合函數(shù)是用 Kotlin 而不是 XML 編寫的,因此它們可以像其他任何 Kotlin 代碼一樣動態(tài)袄简。例如腥放,假設想要構建一個界面,用來問候一些用戶:
此函數(shù)接受名稱的列表绿语,并為每個用戶生成一句問候語秃症。可組合函數(shù)可能非常復雜吕粹≈指蹋可以使用?if?語句來確定是否要顯示特定的界面元素,可以使用循環(huán)匹耕,可以調用輔助函數(shù)聚请。它擁有底層語言的全部靈活性。這種強大的功能和靈活性是 Jetpack Compose 的主要優(yōu)勢之一稳其。
重組
在命令式界面模型中驶赏,如需更改某個 widget,可以在該 widget 上調用 setter 以更改其內部狀態(tài)既鞠。在 Compose 中煤傍,可以使用新數(shù)據(jù)再次調用可組合函數(shù)。這樣做會導致函數(shù)進行重組 -- 系統(tǒng)會根據(jù)需要使用新數(shù)據(jù)重新繪制函數(shù)發(fā)出的 widget嘱蛋。Compose 框架可以智能地僅重組已更改的組件蚯姆。
例如,假設有以下可組合函數(shù)洒敏,它用于顯示一個按鈕:
每次點擊該按鈕時龄恋,調用方都會更新?clicks?的值。Compose 會再次調用 lambda 與?Text?函數(shù)以顯示新值凶伙;此過程稱為“重組”郭毕。不依賴于該值的其他函數(shù)不會進行重組。
重組是指在輸入更改時再次調用可組合函數(shù)的過程镊靴。當函數(shù)的輸入更改時铣卡,會發(fā)生這種情況。當 Compose 根據(jù)新輸入重組時偏竟,它僅調用可能已更改的函數(shù)或 lambda,而跳過其余函數(shù)或 lambda敞峭。通過跳過所有未更改參數(shù)的函數(shù)或 lambda踊谋,Compose 可以高效地重組。
切勿依賴于執(zhí)行可組合函數(shù)所產生的附帶效應旋讹,因為可能會跳過函數(shù)的重組殖蚕。如果這樣做轿衔,可能會遇到奇怪且不可預測的行為。附帶效應是指對應用的其余部分可見的任何更改睦疫。例如害驹,以下操作全部都是危險的附帶效應:
寫入共享對象的屬性
更新?ViewModel?中的可觀察項
更新共享偏好設置
可組合函數(shù)可能會像每一幀一樣頻繁地重新執(zhí)行,例如在呈現(xiàn)動畫時蛤育⊥鸸伲可組合函數(shù)應快速執(zhí)行,以避免在播放動畫期間出現(xiàn)卡頓瓦糕。如果需要執(zhí)行成本高昂的操作(例如從共享偏好設置讀取數(shù)據(jù))底洗,請在后臺協(xié)程中執(zhí)行,并將值結果作為參數(shù)傳遞給可組合函數(shù)咕娄。
例如亥揖,以下代碼會創(chuàng)建一個可組合項以更新?SharedPreferences?中的值。該可組合項不應從共享偏好設置本身讀取或寫入圣勒,于是此代碼將讀取和寫入操作移至后臺協(xié)程中的?ViewModel费变。應用邏輯會使用回調傳遞當前值以觸發(fā)更新。
以下是使用 Compose 時需要注意的一些事項:
????可組合函數(shù)可以按任何順序執(zhí)行圣贸。
????可組合函數(shù)可以并行執(zhí)行挚歧。
? ? 重組會跳過盡可能多的可組合函數(shù)和 lambda。
? ? 重組是樂觀的操作旁趟,可能會被取消昼激。
????可組合函數(shù)可能會像動畫的每一幀一樣非常頻繁地運行。
可組合函數(shù)可以按任何順序執(zhí)行
如果看一下可組合函數(shù)的代碼锡搜,可能會認為這些代碼按其出現(xiàn)的順序運行橙困。但其實未必是這樣。如果某個可組合函數(shù)包含對其他可組合函數(shù)的調用耕餐,這些函數(shù)可以按任何順序運行凡傅。Compose 可以選擇識別出某些界面元素的優(yōu)先級高于其他界面元素,因而首先繪制這些元素肠缔。
可組合函數(shù)可以并行運行
Compose 可以通過并行運行可組合函數(shù)來優(yōu)化重組夏跷。這樣一來,Compose 就可以利用多個核心明未,并以較低的優(yōu)先級運行可組合函數(shù)(不在屏幕上)槽华。
這種優(yōu)化意味著,可組合函數(shù)可能會在后臺線程池中執(zhí)行趟妥。如果某個可組合函數(shù)對?ViewModel?調用一個函數(shù)猫态,則 Compose 可能會同時從多個線程調用該函數(shù)。
為了確保應用正常運行,所有可組合函數(shù)都不應有附帶效應亲雪,而應通過始終在界面線程上執(zhí)行的?onClick?等回調觸發(fā)附帶效應勇凭。
調用某個可組合函數(shù)時,調用可能發(fā)生在與調用方不同的線程上义辕。這意味著虾标,應避免使用修改可組合 lambda 中的變量的代碼,既因為此類代碼并非線程安全代碼灌砖,又因為它是可組合 lambda 不允許的附帶效應璧函。
重組會跳過盡可能多的內容
如果界面的某些部分無效,Compose 會盡力只重組需要更新的部分周崭。這意味著柳譬,它可以跳過某些內容以重新運行單個按鈕的可組合項,而不執(zhí)行界面樹中在其上面或下面的任何可組合項续镇。
每個可組合函數(shù)和 lambda 都可以自行重組美澳。同樣,執(zhí)行所有可組合函數(shù)或 lambda 都應該沒有附帶效應摸航。當需要執(zhí)行附帶效應時制跟,應通過回調觸發(fā)。
重組是樂觀的操作
只要 Compose 認為某個可組合項的參數(shù)可能已更改酱虎,就會開始重組雨膨。重組是樂觀的操作,也就是說读串,Compose 預計會在參數(shù)再次更改之前完成重組聊记。如果某個參數(shù)在重組完成之前發(fā)生更改,Compose 可能會取消重組恢暖,并使用新參數(shù)重新開始排监。
取消重組后,Compose 會從重組中舍棄界面樹杰捂。如有任何附帶效應依賴于顯示的界面舆床,則即使取消了組合操作,也會應用該附帶效應嫁佳。這可能會導致應用狀態(tài)不一致挨队。
確保所有可組合函數(shù)和 lambda 都冪等且沒有附帶效應,以處理樂觀的重組蒿往。
可組合函數(shù)可能會非常頻繁地運行
在某些情況下盛垦,可能會針對界面動畫的每一幀運行一個可組合函數(shù)。如果該函數(shù)執(zhí)行成本高昂的操作(例如從設備存儲空間讀取數(shù)據(jù))瓤漏,可能會導致界面卡頓情臭。
例如省撑,如果 widget 嘗試讀取設備設置赌蔑,它可能會在一秒內讀取這些設置數(shù)百次俯在,這會對應用的性能造成災難性的影響。
如果可組合函數(shù)需要數(shù)據(jù)娃惯,它應為相應的數(shù)據(jù)定義參數(shù)跷乐。然后,可以將成本高昂的工作移至組成操作線程之外的其他線程趾浅,并使用?mutableStateOf?或?LiveData?將相應的數(shù)據(jù)傳遞給 Compose愕提。