Jetpack Compose What and Why, 6個問題
Jetpack Compose What and Why, 6個問題
1.這個技術出現的背景, 初衷, 要達到什麼樣的目標或是要解決什麼樣的問題.
Jetpack Compose是什麼?
它是一個聲明式的UI工具包(declarative UI toolkit for Android).
它的主要目的就是改變之前命令式地(imperatively)寫UI的方法, 改成聲明式(declarative)的.
命令式
Android之前的寫法就叫命令式: view hierarchy是一個UI widgets構成的tree, 當app的狀態改變時, UI需要更新. 通常的做法是通過findViewById()
等手段找到要更新的節點, 通過setText()
, addChild()
, setImageBitmap()
等方法更新控制項的內部狀態. 每個控制項都有自己的內部狀態, 並且暴露getter/setter, 允許程式邏輯和控制項交互.
命令式為什麼不好呢:
- 這樣手動地更新view會增加犯錯的可能性. 如果某一數據在多個地方被渲染, 那麼很可能就忘了更新其中的某個view.
- 很容易創建非法狀態, 比如兩個更新以不期望的方式衝突了. (比如: 一個要更新值, 另一個要移除節點.)
- 維護的複雜度隨著需要更新的view個數而增加.
聲明式
所以, 在過去的幾年裡, 業界一直在探索並且開始轉向一種聲明式的UI模型. 目的就是簡化構建和更新UI.
Jetpack Compose是一個聲明式的UI framework. 該技術的原理是從頭開始重新生成整個螢幕, 然後僅應用必要的更改. 這種方法避免了手動更新stateful view的複雜度.
在Compose的聲明式解決方案里, widgets相對來說是stateless的, 不暴露getter/setter.
實際上, widgets根本不是以對象的形式來暴露的.
當更新UI的時候, 實際上是傳不同的參數調用了composable方法. 當數據改變時, composable負責把當前的應用狀態轉化為UI.
2.這個技術的優勢和劣勢分別是什麼, 或者說, 這個技術的trade-off是什麼.
Compose的優勢:
- 目前Google投入了大量的資源(學習資源, 社區挑戰賽, IDE和編譯器支援, 推出desktop版本等)來推廣Compose, 我們有理由相信Google官方會繼續發力, Compose會成為Android未來的UI標準開發形式.
- Compose的聲明式寫法和Flutter, SwiftUI, React契合, 在支援多個平台的團隊里, 有利於架構思想的統一.
- 聲明式UI, 寫起來更快, 不容易出錯.
- 因為所有的程式碼都是Kotlin寫的(真100%kotlin), 利用了Kotlin的強類型安全, 編譯器會提示很多錯誤.
- 修復和更新了一些舊的API: (Buggy Android Views: Picker, Spinner, EditText, 有一些edge cases. 因為要更改舊的總是很難, 不如創建一個新的. )
- 基於組合的Composable, 比基於繼承的View體系, 更加靈活, 易於復用. 比如可以通過組合來達到復用多個源, 不再受單繼承限制.
以Button為例, 在傳統的UI里, 它是單繼承體系下的一個類: Button -> TextView -> View.
而在Compose的世界裡, 它只是一個@Composable
的方法, 裡面包含了其他composable, surface, row等.
舉例: list->detail兩個介面, 可以通過提取方法參數來達到兩個介面的composable復用. - 支援和View-based UI系統的互相調用. 這樣有兩個優點: 有利於已有項目app的混用和逐步遷移; 當Compose滿足不了需求的時候可以用傳統View作為第二選擇.
- 和Jetpack系列的其他庫都能完美結合. (ViewModel, Kotlin Flow, Coroutines, Paging, Hilt, Navigation…)
劣勢:
- 需要用Canary版本的Android Studio, 目前(2021.5)IDE還是存在一些問題的.
- Gradle也要升級到最新版(7.0.0-alpha15), 不是很穩定, 還有一些問題.
- 版本更新頻繁. Compose alpha版本的一些API已經變了. -> 但是目前已經beta, 最新的Google I/0說7月就會有穩定版發布.
- 相比Android View, 社區支援不夠多. -> 但是Google已經投入了大量的資源來推廣, 社區支援正在飛速增長.
- 雖然Google封裝了material的compose庫, 但還是有一部分View控制項沒有辦法提供, 比如WebView, MapView等.
3.這個技術使用的場景.
Jetpack Compose的使用場景是取代原來的Android View寫法(xml, 在程式碼里實例化View對象再添加到View hierarchy里等),
Jetpack Compose用全新的方式來寫UI, 即將成為Google Android標準寫法.
注意這裡的改變除了UI寫法的改變, 還是一種狀態管理思想的轉變.
聲明式, 單向數據流, 單個數據源.
Android內部的模式近年來一直追求的無外乎就是數據和View的分離, View的無知與自動更新, 清晰的邏輯管理和分離, 可測試性等等.
除了與View強相關的這一層(ViewModel)外, 其他的業務邏輯, 數據交互等被影響不大, 所以即便app決定逐漸遷移到Compose, 也只用管View繪製以及和View相鄰的這一層.
4.技術的組成部分和關鍵點.
Jetpack compose的總體特點:
- 聲明式UI.
f(state)=UI
. - Built on Kotlin.
- Unbundled: 不是和系統綁定的, 而是和Jetpack中的庫一樣, 版本獨立, 可以自己更新維護版本, 也保證了多個系統上的行為一致.
- Built for interop. 可以和已有View互相調用, 適用於現有項目的逐漸遷移.
- 內置控制項和theming, 支援material design.
Composable functions
Compose的使用方法:
定義一系列的composable functions: 接收數據, 發射UI元素.
@Composable
fun Greeting(names: String) {
Text("Hello $name")
}
Composable方法的特點:
- 所有的方法都必須有
@Composable
註解. - 方法參數是數據.
- 通過調用其他composable方法來發射UI元素.
- 方法不返回值, 因為它們在描述螢幕狀態, 而不是構建UI widgets.
- 方法要快, 具有冪等性(調用多少次結果都一樣), 沒有副作用. (fast, idempotent, and side-effect free.) 這就要求方法不會修改外部屬性或者全局變數, 也沒有Random的調用等.
還有一個小區別就是不同於普通的kotlin方法命名規範, composable方法名的首字母要大寫, 因為它此時代表的是一種widget.
Recomposition
Compose framework會很機智地選出有變化, 需要重新繪製的部分.
在Compose中, 如果調用composable function, 傳入了新數據, 會使得方法被recomposed: 這個方法發射的widgets會根據需要進行重新繪製.
因為重新繪製整個UI tree會花銷比較大, 所以實際上composable function只有input改變的才會被重新繪製, 對於那些參數沒有變化的方法和lambda, 其實是skip掉的, 這樣才能提高recompose的效率.
所以, 永遠不要依賴於執行composable function的side-effects, 因為recomposition有可能會被skip掉.
side-effects包括:
- 更新共享對象.
- 更新ViewModel中的observable.
- 更新shared preferences.
由於composable functions有可能會被逐幀執行(比如動畫期間), 所以它應該足夠的快, 如果需要耗時的操作, 可以考慮後台的coroutine.
5.技術的底層原理和關鍵實現.
Compose的幾個特點:
- Composable functions可以以任何順序執行.
- Composable functions可以並行執行.
- Recomposition會儘可能地skip多的composable functions. 如果真的需要side-effects, 考慮用callback.
- Recomposition是樂觀的, 它認為在本次重繪期間不會有新的狀態改變, 如果有新的狀態改變, 它可能會取消當前繪製, 以新的參數重新開始繪製.
- Composable function有可能會被執行得很頻繁(比如動畫), 所以避免耗時操作.
關於Compose的底層原理, 目前還沒找到一個官方的文檔或者架構圖.
因為可能大家都還在學習怎麼使用, 這項技術的底層實現細節還沒有被熱烈討論起來.
這裡有個問題: //stackoverflow.com/questions/58558163/how-does-jetpack-compose-work-under-the-hood
從使用者的角度揣測一下Compose的原理:
雖然Composable使用了註解@Composable
, 但是卻沒有添加註解處理器(kapt), 所以並不是依靠註解在編譯期生成程式碼.
在添加依賴的時候需要在build.gradle
里添加:
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
所以它和kotlin的編譯器有關係.
這個影片: Understanding Compose (Android Dev Summit ’19)在17:18開始講實現原理.
@Composable
更像是一個語言的關鍵字. 可以類比suspend
, 有以下幾個共同點:
- 改變了方法的類型.
- 強制了方法的調用上下文.
部分更新
用了訂製化的Kotlin編譯器插件, 數據改變時, 受數據影響的composable的方法會被重新調用.
6.已有的實現和它之間的對比.
Android View和Jetpack Compose的對比.
Android View和Jetpack Compose的相似點:
- 都是Android UI的寫法.
Jetpack Compose改進了View系統的哪些地方:
- 不再需要擔心深層嵌套. Compose UI不允許多遍的測量(multi-pass measurement), 改善了效率.
Flutter和Jetpack Compose的對比.
Jetpack Compose和Flutter的相似點:
- 聲明式UI,
UI = f(state)
. - 都有web和desktop版本.
- 官方都提供了一些material的控制項和資源, 比如都有個Scaffold, 提供腳手架.
- 都可以和原生混用, 雖然程度不一樣, Compose的混用粒度更細一點.
- 都可以一邊改UI一邊預覽.
Jetpack Compose比Flutter好的地方:
- 基於Kotlin, 相比於Flutter的dart來說, 對Android開發者更友好一些.
- 接著上一點, 因為Compose只改了UI, 所以其他部分的程式碼庫仍然是Android原生程式碼的邏輯, 趁手的第三方庫比較多. 而Flutter, 就得利用其它package來做json解析, 資料庫等等.
- Flutter的stateful widget改狀態必須要在setState里, 使用上不是很方便, 容易出錯.
- Jetpack Compose的Recomposition看上去更智慧一些, 完全自動, 不像Flutter一樣需要開發者自己設置一些flag來表明哪個部分不需要重繪.
- 安裝包體積應該有優勢? 因為Flutter SDK包含了自己的影像引擎, 而Compose是沒有native層面的東西的.
Flutter比Jetpack Compose好的地方:
- Flutter還支援iOS.
- Flutter相比Jetpack Compose稍微成熟一些(Flutter出來的早一些), 是經過一些app的實際上線驗證的.
- Flutter是有自己的圖形引擎Skia的, 繪製會更有效率? (沒有驗證, 純猜測)
Reference
- Thinking in Compose
- Using Jetpack libraries in Compose | Session -> 這個講狀態管理和其他Jetpack庫結合的影片很棒.