Khi gặp phải một lớp không ổn định gây ra các vấn đề về hiệu suất, bạn nên làm cho lớp đó ổn định. Tài liệu này trình bày một số kỹ thuật mà bạn có thể sử dụng để thực hiện việc này.
Bật chế độ bỏ qua hữu hiệu
Trước tiên, bạn nên thử bật chế độ bỏ qua hữu hiệu. Chế độ bỏ qua hữu hiệu cho phép bỏ qua các thành phần kết hợp có tham số không ổn định và là phương pháp dễ nhất để khắc phục các vấn đề về hiệu suất do độ ổn định gây ra.
Hãy xem phần Bỏ qua mạnh để biết thêm thông tin.
Tạo lớp bất biến
Bạn cũng có thể thử làm cho một lớp không ổn định hoàn toàn bất biến.
- Không thể thay đổi: Cho biết một kiểu mà giá trị của mọi thuộc tính không bao giờ có thể thay đổi sau khi một thực thể của kiểu đó được tạo và tất cả các phương thức đều trong suốt về mặt tham chiếu.
- Đảm bảo tất cả các thuộc tính của lớp đều là
valthay vìvarvà thuộc các loại không thể thay đổi. - Các kiểu nguyên thuỷ như
String, IntvàFloatluôn không thể thay đổi. - Nếu không thể, bạn phải sử dụng trạng thái Compose cho mọi thuộc tính có thể thay đổi.
- Đảm bảo tất cả các thuộc tính của lớp đều là
- Ổn định: Cho biết một loại có thể thay đổi. Thời gian chạy Compose không nhận biết được liệu và khi nào bất kỳ thuộc tính công khai hoặc hành vi phương thức nào của kiểu sẽ mang lại kết quả khác với một lệnh gọi trước đó.
Bộ sưu tập bất biến
Một lý do thường gặp khiến Compose coi một lớp là không ổn định là các tập hợp. Như đã lưu ý trong trang Chẩn đoán các vấn đề về tính ổn định, trình biên dịch Compose không thể hoàn toàn chắc chắn rằng các tập hợp như List, Map và Set thực sự là bất biến và do đó, trình biên dịch sẽ đánh dấu các tập hợp này là không ổn định.
Để giải quyết vấn đề này, bạn có thể sử dụng các tập hợp bất biến. Trình biên dịch Compose có hỗ trợ Bộ sưu tập bất biến Kotlinx. Các tập hợp này được đảm bảo sẽ không thay đổi được và trình biên dịch Compose sẽ coi chúng như vậy. Thư viện này vẫn đang ở giai đoạn alpha, vì vậy, bạn có thể thấy những thay đổi đối với API của thư viện.
Hãy xem xét lại lớp không ổn định này trong hướng dẫn Chẩn đoán các vấn đề về tính ổn định:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Bạn có thể tạo tags ổn định bằng cách sử dụng một bộ sưu tập bất biến. Trong lớp, hãy thay đổi loại của tags thành ImmutableSet<String>:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Sau khi thực hiện việc này, tất cả các tham số của lớp đều không thay đổi và trình biên dịch Compose sẽ đánh dấu lớp là ổn định.
Chú giải bằng Stable hoặc Immutable
Một cách có thể giúp giải quyết các vấn đề về độ ổn định là chú thích các lớp không ổn định bằng @Stable hoặc @Immutable.
Chú thích một lớp là ghi đè những gì mà trình biên dịch sẽ suy luận về lớp của bạn. Thao tác này tương tự như !! toán tử trong Kotlin. Bạn nên hết sức cẩn thận về cách sử dụng các chú thích này. Việc ghi đè hành vi của trình biên dịch có thể dẫn đến các lỗi không lường trước được, chẳng hạn như thành phần kết hợp của bạn không kết hợp lại như bạn dự kiến.
Nếu có thể làm cho lớp của bạn ổn định mà không cần chú thích, bạn nên cố gắng đạt được độ ổn định theo cách đó.
Đoạn mã sau đây cung cấp một ví dụ tối thiểu về lớp dữ liệu được chú thích là bất biến:
@Immutable
data class Snack(
…
)
Cho dù bạn sử dụng chú thích @Immutable hay @Stable, trình biên dịch Compose đều đánh dấu lớp Snack là ổn định.
Các lớp được chú thích trong bộ sưu tập
Hãy xem xét một thành phần kết hợp có tham số thuộc kiểu List<Snack>:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Ngay cả khi bạn chú thích Snack bằng @Immutable, trình biên dịch Compose vẫn đánh dấu tham số snacks trong HighlightedSnacks là không ổn định.
Các tham số gặp phải vấn đề tương tự như các lớp khi nói đến các kiểu tập hợp, trình biên dịch Compose luôn đánh dấu một tham số thuộc kiểu List là không ổn định, ngay cả khi đó là một tập hợp các kiểu ổn định.
Bạn không thể đánh dấu một tham số riêng lẻ là ổn định, cũng như không thể chú thích một thành phần kết hợp để luôn có thể bỏ qua. Có nhiều con đường phía trước.
Có một số cách để khắc phục vấn đề về bộ sưu tập không ổn định. Các tiểu mục sau đây trình bày những phương pháp tiếp cận khác nhau này.
Tệp cấu hình
Nếu sẵn sàng tuân thủ hợp đồng ổn định trong cơ sở mã của mình, thì bạn có thể chọn tham gia coi các tập hợp Kotlin là ổn định bằng cách thêm kotlin.collections.* vào tệp cấu hình độ ổn định.
Bộ sưu tập bất biến
Để đảm bảo tính bất biến trong thời gian biên dịch, bạn có thể sử dụng một tập hợp bất biến kotlinx thay vì List.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Nếu không thể sử dụng một bộ sưu tập bất biến, bạn có thể tự tạo bộ sưu tập của riêng mình. Để thực hiện việc này, hãy bao bọc List trong một lớp ổn định được chú thích. Trình bao bọc chung có thể là lựa chọn tốt nhất cho việc này, tuỳ thuộc vào yêu cầu của bạn.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Sau đó, bạn có thể dùng tham số này làm loại tham số trong thành phần kết hợp.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Giải pháp
Sau khi áp dụng một trong hai phương pháp này, trình biên dịch Compose hiện đánh dấu Thành phần kết hợp HighlightedSnacks là cả skippable và 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
)
Trong quá trình kết hợp lại, Compose hiện có thể bỏ qua HighlightedSnacks nếu không có dữ liệu đầu vào nào của thành phần này thay đổi.
Tệp cấu hình độ ổn định
Kể từ Compose Compiler 1.5.5, bạn có thể cung cấp tệp cấu hình của các lớp để xem xét độ ổn định tại thời điểm biên dịch. Điều này cho phép xem xét các lớp mà bạn không kiểm soát, chẳng hạn như các lớp thư viện tiêu chuẩn như LocalDateTime, là ổn định.
Tệp cấu hình là một tệp văn bản thuần tuý có một lớp trên mỗi hàng. Hỗ trợ nhận xét, ký tự đại diện đơn và ký tự đại diện kép.
Ví dụ về cấu hình:
// 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<*,_>
Để bật tính năng này, hãy truyền đường dẫn của tệp cấu hình đến khối tuỳ chọn composeCompiler trong cấu hình trình bổ trợ Gradle của trình biên dịch Compose.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Vì trình biên dịch Compose chạy riêng trên từng mô-đun trong dự án, nên bạn có thể cung cấp các cấu hình khác nhau cho các mô-đun khác nhau nếu cần. Ngoài ra, hãy có một cấu hình ở cấp gốc của dự án và truyền đường dẫn đó đến từng mô-đun.
Nhiều mô-đun
Một vấn đề thường gặp khác liên quan đến cấu trúc nhiều mô-đun. Trình biên dịch Compose chỉ có thể suy luận liệu một lớp có ổn định hay không nếu tất cả các kiểu không nguyên thuỷ mà lớp đó tham chiếu đều được đánh dấu rõ ràng là ổn định hoặc nằm trong một mô-đun cũng được tạo bằng trình biên dịch Compose.
Nếu lớp dữ liệu của bạn nằm trong một mô-đun riêng biệt với lớp giao diện người dùng (đây là phương pháp được đề xuất), thì đây có thể là vấn đề bạn gặp phải.
Giải pháp
Để giải quyết vấn đề này, bạn có thể áp dụng một trong những cách sau:
- Thêm các lớp vào Tệp cấu hình trình biên dịch.
- Bật trình biên dịch Compose trên các mô-đun lớp dữ liệu hoặc gắn thẻ các lớp bằng
@Stablehoặc@Immutablenếu thích hợp.- Việc này liên quan đến việc thêm một phần phụ thuộc Compose vào lớp dữ liệu của bạn. Tuy nhiên, đây chỉ là phần phụ thuộc cho thời gian chạy Compose chứ không phải cho
Compose-UI.
- Việc này liên quan đến việc thêm một phần phụ thuộc Compose vào lớp dữ liệu của bạn. Tuy nhiên, đây chỉ là phần phụ thuộc cho thời gian chạy Compose chứ không phải cho
- Trong mô-đun giao diện người dùng, hãy bao bọc các lớp lớp dữ liệu trong các lớp trình bao bọc dành riêng cho giao diện người dùng.
Vấn đề tương tự cũng xảy ra khi sử dụng các thư viện bên ngoài nếu các thư viện này không sử dụng trình biên dịch Compose.
Không phải thành phần kết hợp nào cũng có thể bỏ qua
Khi khắc phục các vấn đề về độ ổn định, bạn không nên cố gắng làm cho mọi thành phần kết hợp đều có thể bỏ qua. Việc cố gắng làm như vậy có thể dẫn đến việc tối ưu hoá quá sớm, gây ra nhiều vấn đề hơn là khắc phục.
Có nhiều trường hợp mà việc có thể bỏ qua không mang lại lợi ích thực sự và có thể dẫn đến mã khó duy trì. Ví dụ:
- Một thành phần kết hợp không được kết hợp lại thường xuyên hoặc hoàn toàn không được kết hợp lại.
- Một thành phần kết hợp tự gọi các thành phần kết hợp có thể bỏ qua.
- Một thành phần kết hợp có nhiều tham số với các triển khai bằng nhau tốn kém. Trong trường hợp này, chi phí kiểm tra xem có tham số nào đã thay đổi hay không có thể lớn hơn chi phí của một lần kết hợp lại rẻ tiền.
Khi một thành phần kết hợp có thể bỏ qua, thành phần đó sẽ thêm một mức hao tổn nhỏ có thể không đáng. Bạn thậm chí có thể chú thích thành phần kết hợp của mình là không thể khởi động lại trong trường hợp bạn xác định rằng việc có thể khởi động lại sẽ tốn nhiều tài nguyên hơn giá trị mà nó mang lại.