首頁 > 軟體

Android開發Compose remember原理解析

2022-07-29 14:00:49

正文

看過Compose案例或者原始碼的你,相信肯定是見過 remember 了的。顧名思義,Compose是要讓我們的程式碼“記住”東西,那到底是記住什麼呢?要是不 remember,相關功能就實現不了了嗎?

帶著這些問題,來一探究竟吧

隨機色文字

假設有這麼一個“隨機底色文字”的需求:實現一個 Text,其背景色每次啟動都隨機產生,且生命週期內不變

用Compose可以實現如下:

private val items = arrayOf(Color.Red, Color.Gray, Color.Magenta, Color.Blue, Color.Green, Color.Cyan)
@Composable
fun ColorText(name: String) {
    val color = items.random()
    val clicked = mutableStateOf(0)
    Log.d("ct", "ui compose")
    Column {
        Text(
            text = "I'm colored: ${clicked.value}", modifier = Modifier
                .padding(16.dp)
                .background(color)
                .clickable {
                    Log.d("ct", "clicked")
                    clicked.value = clicked.value + 1
                }
        )
    }
}

Text 呼叫 Modifier.background,設定隨機從items中取的顏色,每次新的啟動,都可能不一樣

然而很遺憾,上面的背景色雖然是隨機產生,但是單次生命週期裡,就可能發生變化 —— 比如點選一下文字,如下圖:

更奇怪的是,改變的 clicked 值,並沒有如預期一樣生效,一直是0……

現象和程式碼不一?

原因分析

首先,上述程式碼中的“點選計數” clicked,僅僅是為了測試而存在。因為它是一個 MutableState 物件,點選後改變其值,會觸發Recomposition流程,於是元件重新整理。這樣一來,color 的值將重新由隨機函數算出,我們就看到背景色在變化了

同理,雖然我們好像在點選的時候改變了 clicked 的值,希望像view系統一樣,介面直接響應響應。但是,因為Recomposition的存在,它又被重新構造了,所以其值還是0

正確實現

要實現背景色的整個生命週期固定,但點選文字後,點選計數要更新,應該這麼做:

@Composable
fun ColorText(name: String) {
    val color = remember { items.random() }
    val clicked = remember { mutableStateOf(0) }
    //...
}

僅僅將color和clicked由 remember 包裹起來就解決了問題

remember的原理剖析

前面功能的實現,全仗著remember的加持。它究竟是個啥?

我們先從顏色的remember著手,它呼叫的是這個:

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

其註釋寫明瞭兩個關鍵點,這也是它的功能描述:

  • 記憶由calculation返回的值,僅在composition中執行
  • 在Recomposition過程中,不會重新計算,而是直接返回第1步的值

那麼這又是怎麼做到的呢?

進一步的相關程式碼:

@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: () -> T): T {
    @Suppress("UNCHECKED_CAST")
    return rememberedValue().let {
        // 無效或Empty值時,走if流程,計算並儲存值,否則直接返回
        // remember傳入的invalid為false,所以肯定走值判斷
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}
// 要麼返回Composer.Empty ,要麼返回傳給updateRememberedValue的值
@ComposeCompilerApi
fun rememberedValue(): Any?
// 更新呼叫rememberedValue()後的值,且此值在下一次呼叫rememberedValue()時返回
@ComposeCompilerApi
fun updateRememberedValue(value: Any?)
interface Composer {
    // ....
    companion object {
        /**
         * 用於標記無值的狀態
         */
        val Empty = object {
            override fun toString() = "Empty"
        }
    }
}

從上述程式碼註釋中,基本上已經對原理很清楚了,簡單地說就是:

  • 由composer作為儲存控制
  • 無值時,走初始化邏輯並返回值,同時儲存該值;有值時,直接返回已儲存的值

顏色的“值不變”清楚了,那點選計數的“值變”又是怎麼回事呢?

其實如出一轍,只是點選計數remember的,不是普通值,而是一個 MutableState 型別。這樣一來,它就有兩層含義了:

  • MutableState物件本身在整個composition生命週期不變 —— 即類似普通值的狀態一致性
  • MutableState物件所儲存的實際值,可變 —— 這用以觸發Recomposition,並且獲取更新值

小結

remember 的存在,其實就是 Compose 機制下的產物,用以解決recomposition時的值恢復問題。而因為它的“值不變”特性,還可以用來解決耗時計算的問題,即,耗時計算被remember了,那它就只會執行一次,避免了不必要的額外開銷

以上就是Android開發Compose remember原理解析的詳細內容,更多關於Android開發Compose remember的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com