iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Mobile Development

認真學 Compose - 對 Jetpack Compose 的問題與探索系列 第 15

D15/ 為什麼 remember 是 composable function? - @Composable 是什麼

  • 分享至 

  • xImage
  •  

今天大概會聊到的範圍

  • @Composable
  • compose compiler & runtime

爆個雷:今天的文章只會講前半段,還不會回答 為什麼 remember 是 composable function? 

今天第 15 天,剛好走到鐵人賽的一半,也算認真看 Compose 這個主題好一段時間了。但我一直有個疑惑:到底 @Composable 是什麼東西?

這個問題聽起來可能很蠢,不就是我們在寫 Compose UI 時,那些 UI element 都要加上 Composable ,所以 @Composable 應該是標示某一個 function 將會被視為 View 對吧?

一開始我也是這樣想,直到我看到 remember 的定義:

@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)

等等!remember 為什麼是一個 composable function? 他不是 UI element 啊?

也許,remember 會操控到 UI 的內容勉強還說得過去。但是另一個例子:當我們需要 dp/sp/px 大小單位轉換時,我們需要 LocalDenesity.current,但 LocalDensity.current 的 getter 長這個樣子

inline val current: T
    @ReadOnlyComposable
    @Composable
    get() = currentComposer.consume(this)

為什麼你也是 composable function ???

@Composable 這個 Annotation 做了什麼

簡單來說,@Compoable 這個 annotation 改變了這個 function 的性質。有一個很好的比喻的就是 suspend function ,加上 suspend 關鍵字的 function 會有與一般不同的行為,suspend function 只能在特定的 scope 或是別的 suspend function 中呼叫。@Composable 也是,加上 @Composable 這個 annotation 的 function 會有不同的行為,而且 @Composable 只能在別的 @Composable function  中呼叫。

@Composable
fun composableFunc()


@Composable
fun anotherCompFunc() {
    composableFunc() // ok
}


fun normalFunc(){
    composableFunc() // not allow
}

在我們用到 compose 相關的功能時,我們的專案都會執行 compose compiler。compose compiler 基本上是一個 gradle plugin 在 compile time 加入我們專案的建置。這個 plugin 會找到所有有標上 @Composable annotation 的東西 source 並且檢查這些 composable 是否是在別的 composable function 中呼叫的 source

@Composalbe 並不會觸發 annotation processor,而是可以視之為一個 keyword,就如同上面舉例的 suspend 一樣。他的目的是讓 compiler 知道這個是 composable function,需要特別判斷與處理

Entry point

既然 composable function 一定要在別的 composable function 內才能呼叫,那就會有一個雞生蛋蛋生雞的問題:第一個 composable function 要在哪裡呼叫?
就和 suspend function 需要再 coroutine scope 中呼叫一樣,composable function 也須要有一個 entry point。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeApp()
        }
    }
}

ComponentActivitysetContent function 就是一個 entry point。這個 function 本身不是 composable 但卻接收了一個 composable function,因此,這個 function 內部並沒有直接“呼叫”這個 content ( 這個 composable function ) 而是將它繼續往下傳遞。經過很多層包裝後,最終會

internal fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
    @Suppress("UNCHECKED_CAST")
    val realFn = composable as Function2<Composer, Int, Unit>
    realFn(composer, 1)
}

Composer

以下引用 Leland Richardson 的圖。接下來大部分的的解釋與理來都來自這個文章:https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd

找到 Composable function 的初始執行點了,那這個神秘的 Composer 到底做了什麼呢?在 compose 開始時,可以想像在 compose 啟動開始時,Composer 會建立一個很大的空陣列。

https://ithelp.ithome.com.tw/upload/images/20210929/20141597wsamI8YYJX.png

當 Composer 走訪各個 Composable 時,會一一將各個 Composable 放入這個陣列。並保留陣列尾端的空位

https://ithelp.ithome.com.tw/upload/images/20210929/20141597AVwq83IosC.png

當今天有某個 Composable 觸發了 recomposition 時,composer 就會從頭走訪整個陣列。

https://ithelp.ithome.com.tw/upload/images/20210929/20141597Td6063t45L.png

Composer 取得每個位置的 composable 時,可以依照資料做決定:有可能決定完全不動(假設第一個方形完全沒有改變),有可能決定改變其中的某個參數,但是不影響上下 ( ex. Text ) ( 假設圓形需要改變顏色 )。

https://ithelp.ithome.com.tw/upload/images/20210929/20141597JOjZ71OEDm.png

當今天某個 recomposition 需要觸發 layout 改變增加 child composable 時,composer 會將尾端的空位直接移上來。

https://ithelp.ithome.com.tw/upload/images/20210929/20141597x6injL2RF3.png

從這裡開始,重新將新的 composable 加入陣列中存放。

https://ithelp.ithome.com.tw/upload/images/20210929/20141597Bd588OnEAF.png

這樣存放 composable 的好處,所有的行動(加入 composable、改變 composable 資料、刪除 composable )都是固定時間的 (O(1)) 。唯一耗時的是移動空位 (O(n))。但移動空位通常是有重大 UI 改變時才會觸發,而且每次觸發都是一整個區塊在做改變,因此,Compose UI 團隊才會做這個設計和選擇。


今天的問題還是沒有被回答到。"為什麼 remember 是 composable function?"。但在回答這個之前,我們先碰觸到了 Compose 整個 framework 運作核心的冰山一角。明天預計順著這個邏輯,繼續說明當今天有 State 在 composable tree 中時,composer 怎麼存放資料,state 又怎麼影響 composable。


Reference:


上一篇
D14/ 怎麼做拉動的操作? - Draggable Gesture
下一篇
D16/ 所以到底為什麼 remember 是 composable function? - @Composable 是什麼 part 2
系列文
認真學 Compose - 對 Jetpack Compose 的問題與探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言