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

Khi gặp một lớp không ổn định dẫn đến hiệu suất vấn đề, bạn nên làm cho mã ổ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 để làm điều đó.

Bật chế độ bỏ qua mạnh

Trước tiên, bạn nên cố gắng bật chế độ bỏ qua mạnh. Chế độ bỏ qua 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à cách dễ nhất để khắc phục các vấn đề về hiệu suất do tính ổn định gây ra.

Xem phần Bỏ qua nghiêm ngặt để biết thêm thông tin.

Đảm bảo lớp không thể thay đổi

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

  • Bất biến: Cho biết loại mà giá trị của bất kỳ thuộc tính nào không bao giờ có thể được sau khi tạo một thực thể của loại đó và tất cả các phương thức đều minh bạch về mặt tham chiếu.
    • Hãy đả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 kiểu không thể thay đổi.
    • Các kiểu dữ liệu gốc như String, IntFloat luôn không thể thay đổi.
    • Nếu không thể thực hiện việc này, bạn phải sử dụng trạng thái Compose cho bất kỳ thuộc tính nào có thể thay đổi.
  • Ổ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 nếu và khi một thuộc tính hoặc phương thức công khai bất kỳ của kiểu sẽ mang lại kết quả khác từ 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ự bất biến và do đó đánh dấu chúng là không ổn định.

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

Hãy xem xét lại lớp không ổn định này trong trang Chẩn đoán độ ổ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 tập hợp không thể thay đổi. Trong lớp học, hãy thay đổi loại từ tags đến ImmutableSet<String>:

data class Snack{
    
    val tags: ImmutableSet<String> = persistentSetOf()
    
}

Sau khi làm như vậy, tất cả tham số của lớp đều là không thể thay đổi và Compose trình biên dịch đánh dấu lớp là ổn định.

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

Một cách khả thi để giải quyết các vấn đề về độ ổn định là chú thích các lớp không ổn định với @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ẽ thực hiện nếu không có suy luận về lớp của mình. Nó tương tự như Toán tử !! trong Kotlin. Bạn sẽ rất hãy cẩn thận về cách bạn sử dụng các chú thích này. Ghi đè hành vi của trình biên dịch có thể khiến bạn gặp 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 khi mà 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ú giải, 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ề 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 đá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.

Khi nói đến loại bộ sưu tập, các tham số cũng gặp vấn đề tương tự như lớp, trình biên dịch Compose luôn đánh dấu tham số thuộc loại List là không ổn định, ngay cả khi khi đó là tập hợp các kiểu ổn định.

Bạn không thể đánh dấu một thông số riêng lẻ là ổn định cũng như không thể chú thích một thông số thành phần kết hợp để luôn có thể bỏ qua. Có nhiều lộ trình chuyển tiếp.

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 tiếp cận này.

Tệp cấu hình

Nếu bạn 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 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.

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 một tập hợp kotlinx không thể thay đổi 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. Để làm như vậy, gói List trong một lớp ổn định có chú giải. Một 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 đánh dấu HighlightedSnacks Thành phần kết hợp dưới dạng 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ó dữ liệu đầu vào đã thay đổi.

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

Kể từ Trình biên dịch Compose 1.5.5, một tệp cấu hình gồm các lớp để cho rằng tính ổn định có thể được cung cấp vào 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 trong thư viện chuẩn như LocalDateTime, ổn định.

Tệp cấu hình là tệp văn bản thuần tuý có một lớp trên mỗi hàng. Bình luận, ký tự đại diện đơn và kép đều được hỗ trợ. Dưới đây là cấu hình mẫu:

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider kotlin collections stable
kotlin.collections.*
// 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 ứng dụng Compose các tuỳ chọn của trình biên dịch.

Groovy

kotlinOptions {
    freeCompilerArgs += [
            "-P",
            "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
                    project.absolutePath + "/compose_compiler_config.conf"
    ]
}

Kotlin

kotlinOptions {
  freeCompilerArgs += listOf(
      "-P",
      "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
      "${project.absolutePath}/compose_compiler_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 khác nhau cho các mô-đun nếu cần. Ngoài ra, hãy tạo một ở cấp độ gốc của dự án và chuyển đường dẫn đó đến mỗi .

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 liệu một lớp có ổn định hay không nếu tất cả các kiểu không phải là nguyên gốc tham chiếu được đánh dấu rõ ràng là ổn định hoặc trong một mô-đun được cũng được tạo bằng trình biên dịch Compose.

Nếu lớp dữ liệu nằm trong một mô-đun riêng biệt với lớp giao diện người dùng, đây là đề xuất, đây có thể là vấn đề mà bạn gặp phải.

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 mô-đun lớp dữ liệu hoặc gắn thẻ lớp 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, đây chỉ là phần phụ thuộc cho 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 trình bao bọc dành riêng cho giao diện người dùng khác.

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 sự cố về độ ổn định, bạn không nên cố gắng 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 so với cách khắc phục.

Có nhiều trường hợp quảng cáo 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 mà bản thân nó chỉ gọi 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 đắt bằng thực tế. Trong trường hợp này, chi phí kiểm tra xem có bất kỳ thông số nào có có thể lớn hơn chi phí kết hợp lại rẻ tiền.

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 chi phí nhỏ có thể không đáng kể nó. 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 trong đó bạn xác định rằng có thể khởi động lại sẽ tốn nhiều chi phí hơn giá trị.