Hiệu suất và hệ phân cấp khung hiển thị

Cách bạn quản lý hệ thống phân cấp các đối tượng View có thể ảnh hưởng đáng kể đến hiệu suất của ứng dụng. Trang này mô tả cách đánh giá liệu hệ phân cấp khung hiển thị có làm chậm ứng dụng của bạn hay không, đồng thời cung cấp một số chiến lược để giải quyết các vấn đề có thể phát sinh.

Trang này tập trung vào việc cải thiện bố cục dựa trên View. Để biết thông tin về cách cải thiện hiệu suất trong Jetpack Compose, hãy xem bài viết Hiệu suất trong Jetpack Compose.

Bố cục và đo lường hiệu suất

Quy trình kết xuất bao gồm giai đoạn bố cục và đo lường, trong đó hệ thống xác định vị trí thích hợp cho các mục có liên quan trong hệ phân cấp khung hiển thị của bạn. Phần đo lường của giai đoạn này xác định kích thước và ranh giới của các đối tượng View. Phần bố cục xác định vị trí trên màn hình để đặt đối tượng View.

Cả hai giai đoạn quy trình này đều phải chịu một số chi phí nhỏ cho mỗi lượt xem hoặc bố cục mà chúng xử lý. Trong hầu hết trường hợp, chi phí này rất nhỏ và không ảnh hưởng đáng kể đến hiệu suất. Tuy nhiên, giá trị này có thể lớn hơn khi một ứng dụng thêm hoặc xoá các đối tượng View, chẳng hạn như khi một đối tượng RecyclerView tái chế hoặc sử dụng lại chúng. Chi phí cũng có thể cao hơn nếu một đối tượng View cần xem xét đổi kích thước để đáp ứng các hạn chế. Ví dụ: nếu ứng dụng của bạn gọi phương thức SetText() trên đối tượng View gói văn bản, thì có thể View cần đổi kích thước.

Nếu những trường hợp như vậy mất quá nhiều thời gian, chúng có thể ngăn khung hình hiển thị trong vòng 16 mili giây được cho phép, khiến khung hình bị giảm và ảnh động có hiện tượng giật.

Do không thể di chuyển các hoạt động này sang một luồng worker – ứng dụng của bạn phải xử lý các luồng đó trên luồng chính. Tốt nhất là bạn nên tối ưu hoá chúng để chỉ tiêu tốn ít thời gian nhất có thể.

Quản lý bố cục phức tạp

Bố cục Android hỗ trợ bạn lồng các đối tượng giao diện người dùng trong hệ phân cấp khung hiển thị. Việc lồng nhau này cũng có thể áp dụng chi phí bố cục. Khi ứng dụng của bạn xử lý một đối tượng cho bố cục, ứng dụng cũng sẽ thực hiện quá trình tương tự trên tất cả phần tử con của bố cục.

Đối với một bố cục phức tạp, đôi khi chi phí chỉ phát sinh trong lần đầu tiên hệ thống tính toán bố cục. Ví dụ: khi ứng dụng của bạn tái chế một mục danh sách phức tạp trong đối tượng RecyclerView, hệ thống cần bố trí tất cả đối tượng. Ở một ví dụ khác, những thay đổi nhỏ có thể phân bổ theo chuỗi về phía đối tượng gốc cho đến khi chúng tiếp cận được đối tượng không ảnh hưởng đến kích thước của đối tượng gốc.

Một lý do phổ biến khiến bố cục mất nhiều thời gian là khi hệ phân cấp của các đối tượng View được lồng vào nhau. Mỗi đối tượng bố cục lồng nhau sẽ thêm chi phí vào giai đoạn bố cục. Hệ thống phân cấp của bạn càng phẳng thì càng mất ít thời gian để hoàn tất giai đoạn bố cục.

Bạn nên dùng Layout Editor để tạo ConstraintLayout (thay vì RelativeLayout hoặc LinearLayout) vì cách này thường hiệu quả hơn và giảm việc bố cục lồng nhau. Tuy nhiên, đối với các bố cục đơn giản có thể tạo được bằng cách dùng FrameLayout, bạn nên dùng FrameLayout.

Nếu đang dùng lớp RelativeLayout thì bạn có thể đạt được cùng một hiệu quả với chi phí thấp hơn bằng cách dùng các thành phần hiển thị LinearLayout lồng nhau, không có trọng số. Tuy nhiên, nếu bạn đang dùng thành phần hiển thị LinearLayout lồng nhau và có trọng số thì chi phí bố cục sẽ cao hơn nhiều vì cần nhiều lần truyền bố cục, như giải thích trong phần tiếp theo.

Bạn cũng nên sử dụng RecyclerView thay vì ListView, vì thành phần này có thể tái chế bố cục của các mục danh sách riêng lẻ. Việc này vừa hiệu quả hơn, vừa có thể cải thiện hiệu suất cuộn.

Đánh thuế hai lần

Thường thì khung sẽ thực thi bố cục hoặc giai đoạn đo lường chỉ trong một lần truyền. Tuy nhiên, trong một số trường hợp bố cục phức tạp, khung có thể phải lặp lại nhiều lần trên các phần của hệ phân cấp đòi hỏi nhiều lần truyền để phân giải trước khi định vị các phần tử cuối cùng. Việc phải thực hiện nhiều thao tác lặp lại bố cục và đo lường được gọi là đánh thuế hai lần (double taxation).

Ví dụ: khi bạn dùng vùng chứa RelativeLayout cho phép định vị đối tượng View tương ứng với vị trí của những đối tượng View khác, khung này sẽ thực hiện trình tự sau đây:

  1. Thực thi việc truyền một bố cục và hoạt động đo lường, trong đó khung sẽ tính toán vị trí và kích thước của từng đối tượng con, dựa trên yêu cầu của đối tượng con đó.
  2. Sử dụng dữ liệu này và có tính trọng số của đối tượng để tìm ra vị trí thích hợp của các thành phần hiển thị tương quan.
  3. Thực hiện việc truyền bố cục thứ hai để hoàn tất vị trí của đối tượng.
  4. Chuyển sang giai đoạn tiếp theo của quá trình kết xuất.

Hệ phân cấp khung hiển thị của bạn càng có nhiều cấp thì mức phạt hiệu suất tiềm năng càng lớn.

Như đã đề cập trước đó, ConstraintLayout thường hiệu quả hơn các bố cục khác, ngoại trừ FrameLayout. Ít có trường hợp phải truyền bố cục nhiều lần và trong nhiều trường hợp, bạn sẽ không cần phải lồng các bố cục.

Các vùng chứa ngoài RelativeLayout cũng có thể dẫn đến trường hợp bị đánh thuế hai lần. Ví dụ:

  • Thành phần hiển thị LinearLayout có thể dẫn đến việc truyền hai lần bố cục và đo lường nếu bạn thực hiện theo hướng ngang. Bạn cũng có thể truyền bố cục và hoạt động đo lường hai lần theo hướng dọc nếu thêm measureWithLargestChild. Trong trường hợp này, khung có thể phải truyền lần thứ hai để xử lý kích thước đúng của đối tượng.
  • GridLayout cũng cho phép xác định vị trí tương đối, nhưng thường giúp tránh bị đánh thuế hai lần bằng cách xử lý trước mối quan hệ vị trí trong các thành phần hiển thị con. Tuy nhiên, nếu bố cục sử dụng các trọng số hoặc lấp đầy bằng lớp Gravity, lợi ích của việc xử lý trước đó sẽ bị mất và khung có thể phải thực hiện nhiều lần truyền nếu vùng chứa là RelativeLayout.

Nhiều lần truyền bố cục và hoạt động đo lường không cần thiết gây ra gánh nặng về hiệu suất. Tuy nhiên, chúng cũng có thể trở thành gánh nặng nếu bị đặt sai vị trí. Hãy cẩn thận với những tình huống mà một trong các điều kiện sau áp dụng cho vùng chứa của bạn:

  • Là một thành phần gốc trong hệ phân cấp khung hiển thị của bạn.
  • Có một hệ phân cấp khung hiển thị sâu bên dưới.
  • Có nhiều thực thể của thành phần điền sẵn trên màn hình, tương tự như thành phần con trong đối tượng ListView.

Chẩn đoán các vấn đề về hệ phân cấp khung hiển thị

Hiệu suất bố cục là một vấn đề phức tạp với nhiều thuộc tính. Các công cụ sau đây có thể giúp bạn xác định điểm tắc nghẽn về hiệu suất. Một số công cụ cung cấp thông tin kém chính xác hơn nhưng có thể mang lại gợi ý hữu ích.

Perfetto

Perfetto là công cụ cung cấp dữ liệu về hiệu suất. Bạn có thể mở các dấu vết trên Android trong Giao diện người dùng Perfetto.

Kết xuất GPU cấu hình

Công cụ Kết xuất GPU cấu hình trên thiết bị, có sẵn trên các thiết bị chạy Android 6.0 (API cấp 23) trở lên, có thể mang lại cho bạn thông tin cụ thể liên quan đến điểm tắc nghẽn về hiệu suất. Công cụ này cho bạn biết thời gian của giai đoạn bố cục và đo lường cho mỗi khung hình kết xuất. Dữ liệu này có thể giúp bạn chẩn đoán các vấn đề về hiệu suất trong thời gian chạy, đồng thời giúp bạn xác định những vấn đề về bố cục và hoạt động đo lường mà bạn cần giải quyết.

Trong biểu đồ của dữ liệu đã thu thập, tính năng Kết xuất phân tích GPU dùng màu xanh dương để thể hiện thời gian bố cục. Để biết thêm thông tin về cách sử dụng công cụ này, vui lòng xem bài viết Hướng dẫn về kết xuất phân tích GPU.

Tìm lỗi mã nguồn

Công cụ Tìm lỗi mã nguồn (Lint) của Android Studio có thể giúp bạn biết được tình trạng thiếu hiệu quả trong hệ phân cấp khung hiển thị. Để sử dụng công cụ này, vui lòng chọn Phân tích > Kiểm tra mã, như trong Hình 1.

Hình 1. Chọn Inspect Code (Kiểm tra mã) trong Android Studio.

Thông tin về nhiều mục bố cục xuất hiện trong Android > Lint > Performance (Android > Tìm lỗi mã nguồn > Hiệu suất). Để xem thêm thông tin chi tiết, hãy nhấp vào từng mục để mở rộng và xem thêm thông tin trong ngăn ở bên phải màn hình. Hình 2 minh hoạ một ví dụ về thông tin mở rộng.

Hình 2. Xem thông tin về các vấn đề cụ thể mà công cụ Tìm lỗi mã nguồn xác định được.

Khi nhấp vào một mục, bạn sẽ thấy các sự cố liên quan đến mục đó trong ngăn ở bên phải.

Để hiểu thêm về các chủ đề và vấn đề cụ thể trong lĩnh vực này, vui lòng xem tài liệu về Tìm lỗi mã nguồn.

Layout Inspector

Công cụ Layout Inspector trong Android Studio cung cấp bản trình bày ở dạng hình ảnh về hệ phân cấp khung hiển thị của ứng dụng. Đây là một cách hay để khám phá hệ phân cấp ứng dụng, cung cấp hình ảnh biểu thị rõ ràng về chuỗi gốc của một thành phần hiển thị cụ thể, đồng thời cho phép bạn kiểm tra bố cục mà ứng dụng của bạn tạo.

Các thành phần hiển thị mà Layout Inspector cho thấy cũng có thể giúp xác định vấn đề về hiệu suất phát sinh từ việc đánh thuế hai lần. Đây cũng có thể là cách để bạn xác định chuỗi sâu của các bố cục lồng ghép hoặc các khu vực bố cục có số lượng lớn các phần tử con lồng ghép (một nguồn chi phí hiệu suất tiềm năng khác). Trong những trường hợp này, các giai đoạn bố cục và đo lường có thể gây tốn kém và dẫn đến các vấn đề về hiệu suất.

Để biết thêm thông tin, hãy xem bài viết Gỡ lỗi bố cục bằng công cụ Layout Inspector và tính năng Xác thực bố cục.

Giải quyết các vấn đề về hệ phân cấp khung hiển thị

Khái niệm cơ bản của việc giải quyết vấn đề về hiệu suất phát sinh từ hệ phân cấp khung hiển thị có thể khó khăn hơn khi thực hành. Để tránh cho hệ phân cấp khung hiển thị khỏi các hình phạt nghiêm ngặt về hiệu suất, bạn cần làm phẳng hệ phân cấp khung hiển thị và giảm tình trạng đánh thuế hai lần. Phần này thảo luận một số chiến lược để đạt được các mục tiêu này.

Xoá bố cục lồng thừa thãi

ConstraintLayout là một thư viện Jetpack có nhiều cơ chế để xác định vị trí các thành phần hiển thị trong bố cục. Điều này làm giảm nhu cầu lồng một ConstaintLayout và có thể giúp làm phẳng hệ phân cấp khung hiển thị. Việc làm phẳng hệ phân cấp bằng ConstraintLayout thường đơn giản hơn so với các loại bố cục khác.

Các nhà phát triển thường dùng nhiều bố cục lồng ghép hơn mức cần thiết. Ví dụ như vùng chứa RelativeLayout có thể chứa một phần tử con cũng là vùng chứa RelativeLayout. Việc lồng ghép này là dư thừa và thêm chi phí không cần thiết vào hệ phân cấp khung hiển thị. Tính năng Tìm lỗi mã nguồn có thể gắn cờ vấn đề này cho bạn, giúp giảm thời gian gỡ lỗi.

Dùng tính năng hợp nhất hoặc bao gồm

Một nguyên nhân thường xuyên khiến bố cục lồng ghép thừa là thẻ <include>. Chẳng hạn bạn có thể xác định bố cục có thể sử dụng lại như sau:

<LinearLayout>
    <!-- some stuff here -->
</LinearLayout>

Sau đó, bạn có thể thêm thẻ <include> để thêm mục sau vào vùng chứa gốc:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

Phần trước bao gồm việc lồng ghép bố cục đầu tiên trong bố cục thứ hai.

Thẻ <merge> có thể giúp ngăn chặn sự cố này. Để biết thông tin về thẻ này, hãy xem phần Dùng thẻ <merge>.

Dùng bố cục ít tốn kém hơn

Bạn có thể không điều chỉnh được lược đồ bố cục hiện có để nó không chứa bố cục thừa. Trong một số trường hợp, giải pháp duy nhất có thể là làm phẳng hệ phân cấp của bạn bằng cách chuyển sang một loại bố cục hoàn toàn khác.

Ví dụ: bạn có thể thấy TableLayout cung cấp chức năng tương tự như một bố cục phức tạp hơn với nhiều phần phụ thuộc vị trí. Thư viện Jetpack ConstraintLayout cung cấp chức năng tương tự như RelativeLayout, cộng thêm nhiều tính năng giúp tạo bố cục phẳng và hiệu quả hơn.