在 Jetpack Compose 中,物件可以實作 RememberObserver,以便在與 remember 搭配使用時接收回呼,瞭解物件在組合階層中開始和停止記憶的時間。同樣地,您可以使用 RetainObserver 接收與 retain 搭配使用的物件狀態相關資訊。
對於使用組合階層中生命週期資訊的物件,我們建議採用幾項最佳做法,確認物件在平台中運作良好,並防範濫用行為。具體來說,請使用 onRemembered (或 onRetained) 回呼啟動工作,而不是建構函式;在物件停止記憶或保留時取消所有工作;避免洩漏 RememberObserver 和 RetainObserver 的實作項目,以免意外呼叫。下一節將深入說明這些最佳化建議。
使用 RememberObserver 和 RetainObserver 進行初始化和清除作業
「以 Compose 思考」指南說明瞭組合背後的心智模型。使用 RememberObserver 和 RetainObserver 時,請務必記住組合的兩項行為:
- 重組程序具有樂觀的完成率,也可能會遭到取消
- 所有可組合函式都不得有附帶影響
在 onRemembered 或 onRetained 期間執行初始化副作用,而非建構期間
系統記住或保留物件時,計算 lambda 會在組合期間執行。基於您不會在組合期間執行副作用或啟動協同程式的相同原因,您也不應在傳遞至 remember、retain 和其變化的計算 lambda 中執行副作用。包括記憶體或保留物件的建構函式。
請改為在實作 RememberObserver 或 RetainObserver 時,確認所有效果和啟動的作業都會在 onRemembered 回呼中調度。這與 SideEffect API 的時間相同。此外,這項函式也會確保這些影響只會在套用組合時執行,避免在放棄或延遲重新組合時,發生孤立工作和記憶體洩漏的情況。
class MyComposeObject : RememberObserver { private val job = Job() private val coroutineScope = CoroutineScope(Dispatchers.Main + job) init { // Not recommended: This will cause work to begin during composition instead of // with other effects. Move this into onRemembered(). coroutineScope.launch { loadData() } } override fun onRemembered() { // Recommended: Move any cancellable or effect-driven work into the onRemembered // callback. If implementing RetainObserver, this should go in onRetained. coroutineScope.launch { loadData() } } private suspend fun loadData() { /* ... */ } // ... }
忘記、淘汰或廢棄時的拆除作業
為避免資源洩漏或留下孤立的背景工作,也必須處置記憶體中的物件。對於實作 RememberObserver 的物件,這表示在 onRemembered 中初始化的任何項目,都必須在 onForgotten 中有相符的發布呼叫。
由於組合可以取消,因此實作 RememberObserver 的物件也必須在組合中遭到捨棄時自行清理。如果物件在取消或失敗的組合中由 remember 傳回,就會遭到捨棄。(最常見的情況是使用 PausableComposition,使用 Android Studio 的可組合項預覽工具進行熱重載時,也可能發生這種情況)。
如果捨棄已記憶的物件,系統只會呼叫 onAbandoned (不會呼叫 onRemembered)。如要實作捨棄方法,請處置物件初始化和物件收到 onRemembered 回呼之間建立的所有項目。
class MyComposeObject : RememberObserver { private val job = Job() private val coroutineScope = CoroutineScope(Dispatchers.Main + job) // ... override fun onForgotten() { // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired // should cancel work launched from onRetained. job.cancel() } override fun onAbandoned() { // If any work was launched by the constructor as part of remembering the object, // you must cancel that work in this callback. For work done as part of the construction // during retain, this code should will appear in onUnused. job.cancel() } }
確保 RememberObserver 和 RetainObserver 實作項目為私有
撰寫公開 API 時,請務必謹慎擴充 RememberObserver 和 RetainObserver,建立公開傳回的類別。使用者可能不會在您預期的時間記住物件,也可能以您意想不到的方式記住物件。因此,建議您不要公開實作 RememberObserver 或 RetainObserver 的物件建構函式或工廠函式。請注意,這取決於類別的執行階段型別,而非宣告的型別。請記住,實作 RememberObserver 或 RetainObserver 但轉換為 Any 的物件仍會收到回呼。
不建議使用:
abstract class MyManager
// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }
// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()
建議項目:
abstract class MyManager class MyComposeManager : MyManager() { // Callers that construct this object must manually call initialize and teardown fun initialize() { /*...*/ } fun teardown() { /*...*/ } } @Composable fun rememberMyManager(): MyManager { // Protect the RememberObserver implementation by never exposing it outside the library return remember { object : RememberObserver { val manager = MyComposeManager() override fun onRemembered() = manager.initialize() override fun onForgotten() = manager.teardown() override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ } } }.manager }
記憶物件時的注意事項
除了先前有關 RememberObserver 和 RetainObserver 的建議外,我們也建議您留意並避免意外重新記憶物件,以免影響效能和正確性。以下各節將深入探討特定重新記憶情境,以及為何應避免這些情境。
只記憶物件一次
重新記憶物件可能會有危險。在最佳情況下,您可能會浪費資源記住已記住的值。但如果物件實作 RememberObserver,且意外記住兩次,就會收到超出預期的回呼。這可能會導致問題,因為 onRemembered 和 onForgotten 邏輯會執行兩次,而大多數 RememberObserver 實作都不支援這種情況。如果第二個 remember 呼叫發生在與原始 remember 不同的範圍,且生命週期不同,許多 RememberObserver.onForgotten 實作都會在物件使用完畢前處置該物件。
val first: RememberObserver = rememberFoo()
// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }
這項建議不適用於以遞移方式再次記憶的物件 (也就是會耗用其他記憶物件的記憶物件)。常見的程式碼如下,這是允許的,因為系統會記住不同的物件,因此不會導致回呼意外加倍。
val foo: Foo = rememberFoo() // Acceptable: val bar: Bar = remember { Bar(foo) } // Recommended key usage: val barWithKey: Bar = remember(foo) { Bar(foo) }
假設系統已記住函式引數
函式不應記憶任何參數,因為這可能會導致 RememberObserver 的回呼函式遭到雙重叫用,而且沒有必要。如果必須記住輸入參數,請確認參數未實作 RememberObserver,或要求呼叫端記住引數。
@Composable
fun MyComposable(
parameter: Foo
) {
// Not Recommended: Input should be remembered by the caller.
val rememberedParameter = remember { parameter }
}
這不適用於以遞移方式記住的物件。記憶衍生自函式引數的物件時,請考慮將其指定為 remember 的其中一個鍵:
@Composable fun MyComposable( parameter: Foo ) { // Acceptable: val derivedValue = remember { Bar(parameter) } // Also Acceptable: val derivedValueWithKey = remember(parameter) { Bar(parameter) } }
不要保留已記住的物件
與重新記憶物件類似,您應避免保留已記憶的物件,以延長其生命週期。這是「狀態生命週期」建議的結果:retain 不應與生命週期不符合生命週期保留優惠的物件搭配使用。由於 remembered 物件的生命週期比 retained 物件短,因此不應保留記憶物件。建議您在原始網站保留物件,而不是記住物件。