Tối ưu hiệu suất giao diện Android: Hướng dẫn tối ưu luồng UI

Hướng dẫn tối ưu hiệu suất giao diện Android: Tối ưu hóa luồng UI

Bạn có từng gặp phải tình trạng ứng dụng bị giật khi cuộn trang, nút nhấn không phản hồi, hoặc hoạt ảnh bị rớt khung hình? Những vấn đề này phần lớn liên quan đến việc luồng UI (luồng chính) bị chặn. Bài viết này sẽ hướng dẫn bạn cách giải quyết triệt để vấn đề chặn luồng UI từ ba khía cạnh: tối ưu bố cục, tối ưu kết xuất và xử lý bất đồng bộ, dựa trên các thành phần được chọn lọc trong dự án awesome-android-ui.

Nguyên nhân chính gây ra hiện tượng chặn luồng UI

Luồng UI chịu trách nhiệm xử lý tương tác người dùng và kết xuất giao diện, bất kỳ thao tác tốn thời gian nào cũng sẽ khiến giao diện bị giật. Dựa trên phân tích hiệu suất của hơn 100 thành phần trong awesome-android-ui, chúng ta có thể xác định ba vấn đề phổ biến:

1. Vẽ quá mức (Overdraw)

Khi các thành phần giao diện xếp chồng lên nhau, GPU phải vẽ lại cùng một khu vực nhiều lần. Ví dụ khi sử dụng AndroidMaskableLayout để tạo hiệu ứng che phủ phức tạp, nếu không quản lý tốt cấp độ lớp, có thể dẫn đến hiện tượng vẽ quá mức lên tới 4x trở lên.

2. Cấp độ bố cục quá sâu

Bố cục lồng ghép phức tạp sẽ làm tăng thời gian đo đạc (Measure) và định vị (Layout). Như với ResideLayout - loại bố cục trượt bên cạnh, nếu lồng ghép vượt quá 5 cấp độ, sẽ xuất hiện hiện tượng giật rõ rệt trên các thiết bị tầm thấp.

3. Chặn luồng chính

Thực hiện trực tiếp các yêu cầu mạng, thao tác cơ sở dữ liệu hoặc tính toán phức tạp trên luồng UI. Ví dụ khi sử dụng AndroidSwipeLayout, nếu thực hiện xử lý dữ liệu trong trình nghe sự kiện trượt, sẽ khiến thao tác trượt không mượt mà.

Tối ưu bố cục: Giảm cấp độ và vẽ quá mức

Sử dụng ConstraintLayout thay thế cho LinearLayout

ConstraintLayout có thể giảm đáng kể cấp độ bố cục, được khuyến nghị thay thế cho các trường hợp lồng ghép LinearLayout phức tạp. So sánh với bố cục truyền thống:

Loại bố cục Cấp độ trung bình Thời gian đo đạc
Lồng ghép LinearLayout 5-8 cấp độ 12ms
ConstraintLayout 1-2 cấp độ 3ms

Tối ưu thuộc tính bố cục

  • Loại bỏ nền không cần thiết: `android:background="@null"`
  • Sử dụng thẻ `merge` để giảm cấp độ bố cục gốc
  • Thiết lập hợp lý `clipChildren` và `clipToPadding`

Ví dụ với ExpandableLayout, trước khi tối ưu cấp độ bố cục đạt 6 cấp độ, sau khi tối ưu có thể giảm xuống còn 3 cấp độ:

<!-- Trước khi tối ưu -->
<LinearLayout>
  <FrameLayout>
    <LinearLayout>
      <!-- Nội dung -->
    </LinearLayout>
  </FrameLayout>
</LinearLayout>

<!-- Sau khi tối ưu -->
<merge>
  <FrameLayout>
    <!-- Nội dung -->
  </FrameLayout>
</merge>

Tối ưu kết xuất: Nâng cao hiệu quả vẽ

Giảm số lượng view

Sử dụng RecyclerView thay thế cho ListView, thông qua tái sử dụng view để giảm tiêu hao bộ nhớ và áp lực kết xuất. RecyclerView-FlexibleDivider thể hiện cách giảm chi phí kết xuất của đường phân chia mà vẫn giữ được hiệu ứng thị giác.

Tối ưu View tùy chỉnh

Khi ghi đè phương thức `onDraw` trong View tùy chỉnh cần tránh:

  • Tạo đối tượng mới (như Paint, Path)
  • Tính toán phức tạp (như vòng lặp lớn hoặc phép toán ma trận)

Ví dụ với CircleProgress, mã kết xuất được tối ưu:

// Trước khi tối ưu
@Override
protected void onDraw(Canvas canvas) {
  Paint paint = new Paint(); // Tạo đối tượng mới mỗi lần vẽ
  // ...
}

// Sau khi tối ưu
private Paint paintKetXuat = new Paint(); // Tạo khi khởi tạo

@Override
protected void onDraw(Canvas canvas) {
  // Tái sử dụng paintKetXuat
  // ...
}

Xử lý bất đồng bộ: Di chuyển các thao tác tốn thời gian khỏi luồng UI

Sử dụng AsyncTask và Handler

Các thao tác như yêu cầu mạng, tải hình ảnh... phải được thực hiện bất đồng bộ. Khuyến nghị kết hợp android-Ultra-Pull-To-Refresh khi thực hiện làm mới bằng cách kéo xuống, sử dụng AsyncTask để tải dữ liệu:

new AsyncTask<Void, Void, List<DuLieu>>() {
  @Override
  protected List<DuLieu> doInBackground(Void... thamSo) {
    return layDuLieuTuMang(); // Thực hiện ở nền sau
  }

  @Override
  protected void onPostExecute(List<DuLieu> duLieu) {
    adapter.setDuLieu(duLieu); // Cập nhật ở luồng UI
    layoutLamMoi.hoanTatLamMoi();
  }
}.execute();

Tối ưu tải hình ảnh

Sử dụng các thư viện tải hình ảnh như Landscapist, tự động xử lý nén hình ảnh, lưu cache và tải bất đồng bộ. Tránh tải hình ảnh trực tiếp trong `onBindViewHolder`:

// Cách sử dụng được khuyến nghị
Glide.with(contextViewHienTai.getContext())
  .load(urlHinhAnh)
  .override(300, 200) // Nén kích thước
  .into(imageViewHienTai)

Công cụ kiểm tra hiệu suất và thực tiễn

Ba công cụ kiểm tra hiệu suất chính

  1. Android Studio Profiler: Theo dõi thời gian thực về CPU, bộ nhớ và sử dụng mạng
  2. Systrace: Tạo báo cáo thời gian xử lý tiến trình hệ thống, chính xác đến mili giây
  3. Hierarchy Viewer: Phân tích cấp độ bố cục, như phân tích cấu trúc cấp độ của TileView

Trường hợp thực tế: Tối ưu AdvancedRecyclerView

Một ứng dụng thương mại điện tử sử dụng AdvancedRecyclerView để hiển thị danh sách sản phẩm, tốc độ khung hình khi cuộn chỉ đạt 30fps. Các bước tối ưu:

  1. Sử dụng Systrace phát hiện `onBindViewHolder` mất 20ms
  2. Di chuyển tải hình ảnh sang luồng bất đồng bộ
  3. Giảm cấp độ bố cục item từ 5 cấp độ xuống còn 2 cấp độ
  4. Sau tối ưu tốc độ khung hình ổn định ở mức 58fps

Thẻ: android-performance ui-thread ConstraintLayout recyclerview async-task

Đăng vào ngày 4 tháng 7 lúc 01:29