Khắc phục sự cố về độ ổn định

Khi gặp phải một lớp không ổn định gây ra 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 tính năng bỏ qua hiệu quả

Trước tiên, bạn nên thử bật chế độ bỏ qua hữu hiệu. Chế độ bỏ qua ở mức nghiêm ngặt 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 tính ổn định gây ra.

Hãy xem phần Bỏ qua nhiều để biết thêm thông tin.

Đặt lớp ở chế độ không thể thay đổi

Bạn cũng có thể thử làm cho một lớp không ổn định hoàn toàn không thể thay đổi.

  • Không thể thay đổi: Cho biết một loại mà giá trị của bất kỳ thuộc tính nào không bao giờ có thể thay đổi sau khi một thực thể của loại đó được tạo và tất cả phương thức đều minh bạch về tham chiếu.
    • Đảm bảo tất cả các thuộc tính của lớp đều là val thay vì var và thuộc các loại không thể thay đổi.
    • Các loại nguyên gốc như String, IntFloat luô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.
  • Ổn định: Cho biết một loại có thể thay đổi. Môi trường thời gian chạy Compose không được nhận biết nếu và khi một thuộc tính công khai hoặc hành vi của phương thức bất kỳ của kiểu nào đó mang lại kết quả khác với lệnh gọi trước đó.

Tập hợp bất biến

Một lý do phổ biến khiến Compose coi một lớp là không ổn định là các bộ sưu tập. Như đã lưu ý trên trang Chẩn đoán các vấn đề về độ ổ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, MapSet thực sự không thể thay đổi và do đó đánh dấu chúng là không ổn định.

Để giải quyết vấn đề này, bạn có thể sử dụng các bộ sưu tập không thể thay đổi. Trình biên dịch Compose hỗ trợ Bộ sưu tập bất biến Kotlinx. Các tập hợp này được đảm bảo là không thể thay đổi và trình biên dịch Compose sẽ coi chúng là như vậy. Thư viện này vẫn đang ở giai đoạn alpha, vì vậy, API của thư viện này có thể thay đổi.

Hãy xem xét lại lớp không ổn định này trong hướng dẫn Chẩn đoán sự cố về tính ổn định:

unstable class Snack {
  …
  unstable val tags: Set<String>
  …
}

Bạn có thể làm cho tags ổn định bằng cách sử dụng một bộ sưu tập không thể thay đổi. Trong lớp này, hãy thay đổi loại 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ả tham số của lớp đều không thể thay đổi và trình biên dịch Compose sẽ đánh dấu lớp này là ổn định.

Chú giải bằng Stable hoặc Immutable

Một cách có thể để 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.

Việc chú giải một lớp sẽ ghi đè nội dung mà trình biên dịch sẽ suy luận về lớp của bạn. Tương tự như toán tử !! trong Kotlin. Bạn nên rất 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, chẳng hạn như thành phần kết hợp không kết hợp lại như bạn mong đợi.

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 sự ổn định theo cách đó.

Đoạn mã sau đây cung cấp một ví dụ tối thiểu về một lớp dữ liệu được chú thích là không thể thay đổi:

@Immutable
data class Snack(
…
)

Cho dù bạn sử dụng chú giải @Immutable hay @Stable, trình biên dịch Compose sẽ đá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 chứa 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 cùng một vấn đề như các lớp khi nói đến kiểu bộ sưu tập, trình biên dịch Compose luôn đánh dấu một tham số thuộc loại List là không ổn định, ngay cả khi đó là tập hợp các loại ổn định.

Bạn không thể đánh dấu một tham số riêng lẻ là ổn định, cũng 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 đường dẫn về phía trước.

Có một số cách để bạn khắc phục vấn đề bộ sưu tập không ổn định. Các tiểu mục sau đây trình bày các phương pháp này.

Tệp cấu hình

Nếu sẵn lòng tuân thủ hợp đồng về độ ổn định trong cơ sở mã của mình, thì bạn có thể chọn xem xét 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.

Tập hợp bất biến

Để đảm bảo an toàn cho thời gian biên dịch về tính bất biến, bạn có thể sử dụng bộ sưu tập không thể thay đổi kotlinx, thay vì List.

@Composable
private fun HighlightedSnacks(
    …
    snacks: ImmutableList<Snack>,
    …
)

Wrapper

Nếu không thể sử dụng một tập hợp bất biến, bạn có thể tạo tập hợp của riêng mình. Để thực hiện việc này, hãy gói 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ể sử 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 thực hiện một trong các phương pháp này, trình biên dịch Compose hiện sẽ đánh dấu Thành phần kết hợp HighlightedSnacks là cả skippablerestartable.

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ó giá trị đầu vào nào thay đổi.

Tệp cấu hình về độ ổn định

Kể từ Trình biên dịch Compose 1.5.5, bạn có thể cung cấp tệp cấu hình của các lớp cần xem là ổ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 chuẩn như LocalDateTime, là ổn định.

Tệp cấu hình là tệp văn bản thuần tuý, mỗi dòng chứa một lớp. Hỗ trợ nhận xét, ký tự đại diện đơn và 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 chuyển đường dẫn của tệp cấu hình đến khối tuỳ chọn composeCompiler của cấu hình trình bổ trợ Gradle cho 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 trên từng mô-đun trong dự án riêng biệt, nên bạn có thể cung cấp cấu hình riêng cho từng mô-đun nếu cần. Ngoài ra, hãy có một cấu hình ở cấp cơ sở của dự án và truyền đường dẫn đó đến từng mô-đun.

Nhiều mô-đun

Một vấn đề phổ biến 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 xem một lớp có ổn định hay không nếu tất cả các loại không phải loại gốc mà lớp đó tham chiếu đều được đánh dấu rõ ràng là ổn định hoặc 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ì bạn có thể gặp phải vấn đề này.

Giải pháp

Để giải quyết vấn đề này, bạn có thể thực hiện một trong các phương pháp sau:

  1. Thêm các lớp vào tệp cấu hình Trình biên dịch.
  2. 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 của bạn bằng @Stable hoặc @Immutable khi 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. Tuy nhiên, đó chỉ là phần phụ thuộc cho môi trường thời gian chạy Compose chứ không phải cho Compose-UI.
  3. Trong mô-đun giao diện người dùng, hãy gói 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 thư viện bên ngoài nếu các thư viện đó không 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 tìm cách khắc phục vấn đề về độ ổn định, bạn không nên tìm cách làm cho mọi thành phần kết hợp 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á 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ự nào và có thể khiế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 không được kết hợp lại.
  • Một thành phần kết hợp chỉ 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 phương thứ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í kết hợp lại rẻ.

Khi một thành phần kết hợp có thể bỏ qua, thao tác này sẽ thêm một khoản hao tổn nhỏ có thể không đáng giá. Bạn thậm chí có thể chú thích thành phần kết hợp là không khởi động lại được trong trường hợp bạn xác định rằng việc khởi động lại sẽ tốn nhiều chi phí hơn.