Trong Jetpack Compose, một đối tượng có thể triển khai RememberObserver để nhận các lệnh gọi lại khi được dùng với remember nhằm biết thời điểm đối tượng bắt đầu và ngừng được ghi nhớ trong hệ phân cấp thành phần. Tương tự, bạn có thể dùng RetainObserver để nhận thông tin về trạng thái của một đối tượng được dùng với retain.
Đối với các đối tượng sử dụng thông tin vòng đời này từ hệ phân cấp thành phần, bạn nên áp dụng một số phương pháp hay nhất để xác minh rằng các đối tượng của bạn hoạt động như những thành phần tốt trong nền tảng và chống lại hành vi sử dụng sai mục đích. Cụ thể, hãy sử dụng các lệnh gọi lại onRemembered (hoặc onRetained) để chạy công việc thay vì hàm khởi tạo, huỷ tất cả công việc khi các đối tượng ngừng được ghi nhớ hoặc giữ lại và tránh rò rỉ các phương thức triển khai RememberObserver và RetainObserver để tránh các lệnh gọi vô tình. Phần tiếp theo sẽ giải thích chi tiết hơn về những đề xuất này.
Khởi tạo và dọn dẹp bằng RememberObserver và RetainObserver
Hướng dẫn Tư duy trong Compose mô tả mô hình tư duy đằng sau thành phần. Khi làm việc với RememberObserver và RetainObserver, bạn cần lưu ý 2 hành vi của thành phần:
- Việc kết hợp lại là khả quan và có thể bị huỷ
- Tất cả hàm có khả năng kết hợp không được có tác dụng phụ
Chạy các tác dụng phụ khởi tạo trong onRemembered hoặc onRetained, chứ không phải trong quá trình tạo
Khi các đối tượng được ghi nhớ hoặc giữ lại, biểu thức lambda tính toán sẽ chạy như một phần của thành phần. Vì những lý do tương tự mà bạn không thực hiện hiệu ứng phụ hoặc khởi chạy một coroutine trong quá trình kết hợp, bạn cũng không nên thực hiện hiệu ứng phụ trong lambda tính toán được truyền đến remember, retain và các biến thể của chúng.
Điều này bao gồm cả việc sử dụng như một phần của hàm khởi tạo cho các đối tượng được ghi nhớ hoặc giữ lại.
Thay vào đó, khi triển khai RememberObserver hoặc RetainObserver, hãy xác minh rằng tất cả các hiệu ứng và công việc đã khởi chạy đều được gửi trong lệnh gọi lại onRemembered.
Điều này mang lại thời gian tương tự như các API SideEffect. Thao tác này cũng đảm bảo rằng các hiệu ứng này chỉ thực thi khi thành phần kết hợp được áp dụng, giúp ngăn chặn các công việc bị bỏ rơi và rò rỉ bộ nhớ nếu quá trình kết hợp lại bị bỏ dở hoặc bị hoãn lại.
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() { /* ... */ } // ... }
Tháo dỡ khi bị quên, ngừng hoạt động hoặc bỏ hoang
Để tránh rò rỉ tài nguyên hoặc bỏ lại các công việc ở chế độ nền, bạn cũng phải loại bỏ các đối tượng đã lưu. Đối với các đối tượng triển khai RememberObserver, điều này có nghĩa là mọi thứ được khởi tạo trong onRemembered đều phải có một lệnh gọi phát hành phù hợp trong onForgotten.
Vì có thể huỷ thành phần, nên các đối tượng triển khai RememberObserver cũng phải tự dọn dẹp nếu bị bỏ rơi trong thành phần. Một đối tượng sẽ bị loại bỏ khi được remember trả về trong một thành phần bị huỷ hoặc không thành công. (Điều này thường xảy ra nhất khi sử dụng PausableComposition và cũng có thể xảy ra khi sử dụng tính năng tải lại nhanh với công cụ xem trước thành phần kết hợp của Android Studio.)
Khi một đối tượng đã lưu bị loại bỏ, đối tượng đó chỉ nhận được lệnh gọi đến onAbandoned (và không nhận được lệnh gọi đến onRemembered). Để triển khai phương thức loại bỏ, hãy loại bỏ mọi thứ được tạo giữa thời điểm đối tượng được khởi chạy và thời điểm đối tượng sẽ nhận được lệnh gọi lại 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() } }
Giữ riêng tư cho các hoạt động triển khai RememberObserver và RetainObserver
Khi viết các API công khai, hãy thận trọng khi mở rộng RememberObserver và RetainObserver trong việc tạo các lớp được trả về công khai. Người dùng có thể không nhớ đối tượng của bạn khi bạn mong đợi hoặc có thể nhớ đối tượng của bạn theo cách khác với dự định của bạn. Vì lý do này, bạn không nên hiển thị các hàm khởi tạo hoặc hàm tạo cho các đối tượng triển khai RememberObserver hoặc RetainObserver. Xin lưu ý rằng điều này phụ thuộc vào loại thời gian chạy của một lớp, chứ không phải loại được khai báo – việc ghi nhớ một đối tượng triển khai RememberObserver hoặc RetainObserver nhưng được truyền đến Any vẫn khiến đối tượng nhận được các lệnh gọi lại.
Không nên:
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()
Được đề xuất:
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 }
Những điều cần cân nhắc khi ghi nhớ các đối tượng
Ngoài các đề xuất trước đó liên quan đến RememberObserver và RetainObserver, bạn cũng nên lưu ý và tránh vô tình ghi nhớ lại các đối tượng để đảm bảo hiệu suất và tính chính xác. Các phần sau đây sẽ đi sâu hơn vào các trường hợp cụ thể về việc nhớ lại và lý do bạn nên tránh những trường hợp đó.
Chỉ ghi nhớ các đối tượng một lần
Việc nhớ lại một đối tượng có thể gây nguy hiểm. Trong trường hợp tốt nhất, bạn có thể đang lãng phí tài nguyên khi ghi nhớ một giá trị đã được ghi nhớ. Nhưng nếu một đối tượng triển khai RememberObserver và được ghi nhớ hai lần ngoài dự kiến, thì đối tượng đó sẽ nhận được nhiều lệnh gọi lại hơn dự kiến. Điều này có thể gây ra vấn đề, vì logic onRemembered và onForgotten sẽ thực thi hai lần và hầu hết các cách triển khai RememberObserver đều không hỗ trợ trường hợp này. Nếu lệnh gọi remember thứ hai xảy ra trong một phạm vi khác có vòng đời khác với remember ban đầu, thì nhiều cách triển khai RememberObserver.onForgotten sẽ loại bỏ đối tượng trước khi đối tượng được sử dụng xong.
val first: RememberObserver = rememberFoo()
// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }
Lời khuyên này không áp dụng cho các đối tượng được ghi nhớ lại một cách gián tiếp (chẳng hạn như các đối tượng được ghi nhớ sử dụng một đối tượng được ghi nhớ khác). Bạn thường viết mã có dạng như sau, mã này được phép vì một đối tượng khác đang được ghi nhớ và do đó không gây ra hiện tượng tăng gấp đôi lệnh gọi lại không mong muốn.
val foo: Foo = rememberFoo() // Acceptable: val bar: Bar = remember { Bar(foo) } // Recommended key usage: val barWithKey: Bar = remember(foo) { Bar(foo) }
Giả sử các đối số của hàm đã được ghi nhớ
Một hàm không nên ghi nhớ bất kỳ tham số nào của hàm đó vì điều này có thể dẫn đến việc gọi lại hai lần cho RememberObserver và vì điều này là không cần thiết. Nếu phải ghi nhớ một tham số đầu vào, hãy xác minh rằng tham số đó không triển khai RememberObserver hoặc yêu cầu phương thức gọi ghi nhớ đối số của chúng.
@Composable
fun MyComposable(
parameter: Foo
) {
// Not Recommended: Input should be remembered by the caller.
val rememberedParameter = remember { parameter }
}
Điều này không áp dụng cho các đối tượng được ghi nhớ một cách bắc cầu. Khi ghi nhớ một đối tượng bắt nguồn từ các đối số của hàm, hãy cân nhắc chỉ định đối tượng đó làm một trong các khoá cho remember:
@Composable fun MyComposable( parameter: Foo ) { // Acceptable: val derivedValue = remember { Bar(parameter) } // Also Acceptable: val derivedValueWithKey = remember(parameter) { Bar(parameter) } }
Đừng giữ lại một đối tượng đã được ghi nhớ
Tương tự như việc nhớ lại một đối tượng, bạn nên tránh giữ lại một đối tượng đã được ghi nhớ để cố gắng kéo dài tuổi thọ của đối tượng đó. Đây là hệ quả của lời khuyên trong Thời gian tồn tại của trạng thái: retain không được dùng với các đối tượng có thời gian tồn tại không khớp với thời gian tồn tại mà hoạt động giữ lại cung cấp. Vì các đối tượng remembered có thời gian tồn tại ngắn hơn các đối tượng retained, nên bạn không nên giữ lại một đối tượng đã ghi nhớ. Thay vào đó, hãy giữ lại đối tượng tại trang web nguồn thay vì ghi nhớ đối tượng đó.