composable的生命週期

在這個頁面中,您會瞭解可composable的生命週期,以及 Compose 如何決定composable是否需要重新建構。

生命週期總覽

管理狀態說明文件中所述,重新建構指的是應用程式適用的使用者介面,並透過執行元件執行。Composition是描述您的UI的composable的樹狀結構。

當 Jetpack Compose 首次執行您的composable時,在初始composition期間,系統會追蹤您呼叫的composable,以在Composition內容中描述您的 UI。等到應用程式的狀態變更時,Jetpack Compose 就會安排重新composition。重新安排時,Jetpack Compose 重新執行可能為改變狀態而變更的composable,然後更新Composition以反映任何變更。

Composition只能由初始Composition製作,並透過Composition更新。修改Composition的唯一方式是重新Composition。

顯示composable生命週期的圖表

圖 1。 Composition內Composable的生命週期。而這會進入Composition狀態,被compose 0 次以上,然後留下Composition的內容。

一般而言,您更改 State<T> 物件後才會觸發重新composition。然後Compose追蹤這些composable,並在Composition內容中讀取這個特定 State<T> 的composable,以及無法呼叫的可略過內容。

如果多次呼叫composable的元件,系統就會將多個執行個體放入Composition中。每個呼叫在Composition內容中都有各自的生命週期。

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

呈現前程式碼片段中元素階層的圖表

圖 2。代表Composition的 MyComposable。如果多次呼叫composable,系統就會將多個執行個體放入Composition中。具有不同顏色的元素代表個別執行個體。

Composition的composable的解剖

Composition的例項是由其呼叫網站識別。Compose 編譯器會將每個呼叫網站視為具有差異。呼叫多個呼叫網站的composable,用於建立Composition的多個composable執行個體。

如果在composition期間,composable呼叫與先前不同的composable,Compose 會辨識該呼叫或不該呼叫的composable,並且對於已經呼叫的composable再次於composition被呼叫,Compose 會避免在輸入內容維持不變的情況下重新composition

保留身分是將副作用與composable建立關聯在一起的重要關係,這樣使用者才能順利完成工作,不必每次都開始重新composition。

請參考以下範例:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

在上方的程式碼片段中,LoginScreen 會以條件方式呼叫 LoginError composable,並且一律呼叫 LoginInput composable。每個呼叫都有專屬的呼叫網站和來源位置,編譯器將會用來識別該網路。

這張圖表顯示如何在 ShowError 旗標變更為 true 時重新編寫上述程式碼。新增了 LoginError composable,但其他不可調的元件則未重新composable。

圖 3。 當狀態發生變更和重新composition時,composable中的 LoginScreen 代表。相同顏色則表示未經過重新composition。

雖然系統會優先呼叫 LoginInput,但第二個呼叫仍會保留,但 LoginInput 執行個體會留存在composition中。此外,由於 LoginInput 的整個參數在整個參數中都發生過變更,因此 Compose 會略過對 LoginInput 的呼叫。

新增其他資訊以協助智慧重新composition

多次呼叫合成composition也會多次加到composition中。多次從同一呼叫網站呼叫composable時,Compose 並沒有取得任何資訊可用來識別該composable的每次呼叫,因此除了執行網站外,執行順序還會用於執行網站這些執行個體會有所不同。這類行為有時是必要的,但在某些情況下可能會產生不必要的行為。

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

在上述範例中,除了呼叫網站之外,Compose 還會使用執行順序,讓執行個體在composable中保持不同。如果將新的 movie 新增至清單的「底部」,由於 協議在執行個體中的位置尚未變更,因此 Compose 可重複使用Composition中的執行個體,這些執行個體適用的 movie 輸入相同。

這張圖表顯示瞭如果將新的元素加到清單底部,程式碼的compose方式。這份清單中的其他項目並未變更排名,也不會建議重新compose。

圖 4。. 在清單底部加入新元素時,代表composition中的 MoviesScreen。Composition中的 MovieOverview composable可以重複使用。在 MovieOverview 中使用相同的顏色,表示可組合的未重新composable。

不過,如果 movies 清單透過新增至頂端中間的方式加以變更,請移除或重新排序項目,就會導致在所有 MovieOverview 呼叫中,其輸入參數已變更的位置。舉例來說,如果 MovieOverview 使用副作用擷取電影圖片非常重要如果在動作執行期間進行重組,系統就會取消並重新開始。

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

這張圖表顯示瞭如果將新的元素加到清單頂端,程式碼的重compose的方式。清單中的每個其他項目都會變更位置,因此必須重新compose。

圖 5。在清單中加入新元素時,代表compose中的 MoviesScreen。無法重複使用 MovieOverview composable,且所有副作用都會重新啟動。MovieOverview 中的不同顏色表示composable的compose。

在理想情況下,我們希望將 MovieOverview 的執行個體視為連結至該執行個體的 movie 身分。如果我們重新排序電影清單,最好會重新排列composition樹狀結構中的執行個體,而不是將不同的 MovieOverview composable組成不同的電影執行個體。Compose 可讓您向執行階段指明您要用來識別樹狀結構中特定部分的值:key composable的值。

透過呼叫由可傳入的一或多個值所發出的鍵來包裝程式碼區塊,系統會將這些值合併,以在composable中識別該執行個體。key 的值不必「全域」重複,因為在呼叫網站的元件組合中,該值可以重複。在這個範例中,movie必須設有key這個數字在movies;同意分享key其他一些元件組合

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

如上所述,即使清單上的元素發生變更,Compose 也會辨識對 MovieOverview 的個別呼叫,且可以重複使用。

這張圖表顯示瞭如果將新的元素加到清單頂端,程式碼的重新compose的方式。由於清單項目是以鍵識別,因此 Compose 就不是要重新編排這些項目,即使其位置有所變更也一樣。

圖 6。在清單中加入新元素時,代表compose中的 MoviesScreen。由於 MovieOverview 元件含有專屬金鑰,因此 Compose 可辨識哪些 MovieOverview 執行個體並未變更,且可重複使用。他們的副作用會繼續執行。

某些composable已內建 key composable。舉例來說,LazyColumn 接受在 items DSL 中指定自訂 key

@Composable
fun MoviesScreen(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

如果輸入內容未變更,則可略過

如果composable中已含有可構成的組合,當所有輸入保持穩定且未變更時,就可以略過重新composition。

固定類型必須符合下列合約規範:

  • 兩個執行個體的 equals 結果「會」與同一個兩個執行個體相同。
  • 如果類型的公開資源有所異動,系統會通知Composition。
  • 所有公開資源類型的類型都是穩定版本。

合約中,Compose編譯器會將幾個較常使用的類型視為穩定,即使使用 @Stable 註解並未明確將定義視為穩定版:

  • 所有原始值類型:BooleanIntLongFloatChar 等。
  • 字串
  • 所有函式類型 (lambdas)

所有這類型均能遵循穩定版的合約,因為這些變更無法變更。由於不可變更的類型一律不會改變,因此他們不用費心告知變更內容,因此您可以輕鬆遵循合約內容。

其中一個固定類型是穩定版,但可以成為 Compose 的 MutableState 類型。如果將值保存在MutableState,整體狀態物件會視為穩定,因為當使用者撰寫程式碼變更時, .value屬性State的 Google Ads 新帳戶重新申請驗證。

當所有已傳遞為組合的參數類型保持穩定時,系統會根據 UI 樹狀結構中的composable位置,比較參數值。如果上次呼叫後所有值都未變更,則系統會略過重新 composition。

只有在能夠證明類型時,Compose 才會認定其類型穩定。舉例來說,介面通常被視為不穩定,而包含可變動公開屬性的類型,在實作中可能無法變更。

如果 Compose 無法推論類型穩定,但您想強制 Compose 判定為穩定,請使用 @Stable 註解標記。

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

在上方的程式碼片段中,由於 UiState 是介面,因此 Compose 通常可能會判定此類型不穩定。新增 @Stable 註解即表示 Compose 這個類型很穩定,以便 Compose 支援智慧重新composition。也就是說,當介面做為參數類型使用時,Compose 會將所有實作項目視為穩定版。