如果遇到導致效能問題的不穩定類別,請將其設為穩定。本文將概述幾種可用的方法。
啟用嚴格略過模式
建議你先啟用嚴格略過模式。嚴格略過模式可略過含有不穩定參數的可組合項,是修正穩定性造成效能問題最簡單的方法。
詳情請參閱強制略過。
讓類別不可變更
您也可以嘗試讓不穩定的類別完全不可變動。
- 不可變動:表示類型,其中任何屬性的值在建構該類型的執行個體後,就永遠無法變更,且所有方法在參照上都是透明的。
- 請確認類別的所有屬性都是
val而非var, 且屬於不可變動的型別。 String, Int和Float等原始型別一律為不可變動。- 如果無法這麼做,您必須針對任何可變動的屬性使用 Compose 狀態。
- 請確認類別的所有屬性都是
- 穩定:表示可變動的型別。如果任何型別的公開屬性或方法行為會產生與先前叫用不同的結果,Compose 執行階段不會察覺。
不可變更的集合
Compose 將類別視為不穩定的常見原因為集合。如「診斷穩定性問題」頁面所述,Compose 編譯器無法完全確定 List, Map 和 Set 等集合是否真的不可變動,因此會將其標示為不穩定。
如要解決這個問題,可以使用不可變動的集合。Compose 編譯器支援 Kotlinx Immutable Collections。這些集合保證不可變動,Compose 編譯器也會這樣處理。這個程式庫仍處於 Alpha 版,因此 API 可能會有所變更。
請再次參考「診斷穩定性問題」指南中的不穩定類別:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
您可以使用不可變動的集合,讓 tags 保持穩定。在類別中,將 tags 的類型變更為 ImmutableSet<String>:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
完成後,所有類別的參數都會變成不可變動,而 Compose 編譯器會將類別標示為穩定。
使用 Stable 或 Immutable 註解
如要解決穩定性問題,其中一個方法是使用 @Stable 或 @Immutable 註解不穩定的類別。
為類別加上註解,會覆寫編譯器對類別的推論。這與 Kotlin 中的 !! 運算子類似。使用這些註解時,請務必非常小心。覆寫編譯器行為可能會導致無法預測的錯誤,例如可組合項未在預期時間重組。
如果可以在沒有註解的情況下讓類別穩定,請盡量以這種方式達成穩定性。
以下程式碼片段提供加上不可變動註解的資料類別範例:
@Immutable
data class Snack(
…
)
無論您使用 @Immutable 或 @Stable 註解,Compose 編譯器都會將 Snack 類別標示為穩定。
集合中的註解類別
假設可組合函式包含 List<Snack> 類型的參數:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
即使使用 @Immutable 註解 Snack,Compose 編譯器仍會將 HighlightedSnacks 中的 snacks 參數標示為不穩定。
就集合型別而言,參數會面臨與類別相同的問題,即使是穩定型別的集合,Compose 編譯器一律會將 List 型別的參數標示為不穩定。
您無法將個別參數標示為穩定,也無法註解可組合函式,使其一律可略過。有多種前進路徑。
您可以透過下列幾種方式解決集合不穩定的問題。以下各小節將概略說明這些不同做法。
設定檔
如果您願意遵守程式碼集中的穩定性合約,可以在穩定性設定檔中新增 kotlin.collections.*,選擇將 Kotlin 集合視為穩定。
不可變更的集合
如要確保編譯時間的不變性安全,可以改用 kotlinx 不可變動的集合,而非 List。
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
如果無法使用不可變動的集合,可以自行建立。如要這麼做,請將 List 包裝在註解穩定的類別中。視您的需求而定,一般包裝函式可能是最佳選擇。
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
然後您就可以在可組合函式中,將這個做為參數的型別。
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
解決方案
採用任一方法後,Compose 編譯器現在會將 HighlightedSnacks 可組合函式標示為 skippable 和 restartable。
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
stable snacks: ImmutableList<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
在重組期間,如果 HighlightedSnacks 的任何輸入內容都沒有變更,Compose 現在可以略過 HighlightedSnacks。
穩定性設定檔
從 Compose 編譯器 1.5.5 版開始,您可以在編譯時提供要視為穩定的類別設定檔。這樣一來,您就能將無法控制的類別 (例如 LocalDateTime 等標準程式庫類別) 視為穩定。
設定檔為純文字檔案,每列一個類別。支援註解、單一和雙萬用字元。
設定範例如下:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
如要啟用這項功能,請將設定檔路徑傳遞至 Compose 編譯器 Gradle 外掛程式設定的 composeCompiler 選項區塊。
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
由於 Compose 編譯器會分別在專案中的每個模組上執行,因此您可以視需要為不同模組提供不同設定。或者,您可以在專案的根層級設定一個設定,然後將該路徑傳遞至每個模組。
多個模組
另一個常見問題是多模組架構。只有在類別參照的所有非基本類型都明確標示為穩定,或位於同樣使用 Compose 編譯器建構的模組中,Compose 編譯器才能推斷類別是否穩定。
如果資料層與 UI 層位於不同模組 (建議採用這種做法),您可能會遇到這個問題。
解決方案
如要解決這個問題,可以採取下列任一做法:
- 將類別新增至編譯器設定檔。
- 在資料層模組上啟用 Compose 編譯器,或視需要使用
@Stable或@Immutable標記類別。- 這包括在資料層中新增 Compose 依附元件。不過,這只是 Compose 執行階段的依附元件,而非
Compose-UI的依附元件。
- 這包括在資料層中新增 Compose 依附元件。不過,這只是 Compose 執行階段的依附元件,而非
- 在 UI 模組中,將資料層類別包裝在 UI 專屬的包裝函式類別中。
如果外部程式庫未使用 Compose 編譯器,也會發生相同問題。
並非每個可組合函式都應可略過
修正穩定性問題時,請勿嘗試讓每個可組合函式都可略過。如果過早進行最佳化,可能會導致問題越修越多。
在許多情況下,可略過性不會帶來任何實質好處,反而可能導致難以維護的程式碼。例如:
- 不會或很少重組的可組合項。
- 可組合函式本身只會呼叫可略過的可組合函式。
- 具有大量參數的可組合項,且實作成本高昂。在這種情況下,檢查是否有任何參數變更的成本,可能會超過重新組合的成本。
可略過的可組合項會增加少量負擔,可能不值得。如果判斷可重新啟動的成本高於價值,您甚至可以將可組合函式註解為「不可重新啟動」。