Tối ưu hoá hình ảnh bitmap

Việc xử lý hình ảnh có thể nhanh chóng gây ra vấn đề về hiệu suất nếu bạn không cẩn thận. Ngay cả một hình ảnh nhỏ ở định dạng nén như JPG hoặc PNG cũng có thể chuyển thành một bitmap lớn khi được giải mã để hiển thị. Nếu không sử dụng đồ hoạ một cách hiệu quả, bạn có thể gặp phải các vấn đề về bộ nhớ, điều này có thể ảnh hưởng đến hiệu suất của ứng dụng và các ứng dụng khác trên thiết bị. Hãy làm theo các phương pháp hay nhất này để đảm bảo ứng dụng của bạn hoạt động hiệu quả nhất.

Sử dụng thư viện tải hình ảnh

Bạn có thể cải thiện hiệu suất của ứng dụng bằng cách sử dụng các thư viện tải hình ảnh như Coil (đối với các dự án ưu tiên Kotlin) hoặc Glide (đối với các dự án Java). Những thư viện này giúp giảm mức sử dụng bộ nhớ của ứng dụng bằng cách thực hiện các thao tác như lưu hình ảnh vào bộ nhớ đệm, giảm tần số lấy mẫu đồ hoạ khi cần và tái chế các đối tượng đồ hoạ.

Giảm độ phân giải của hình ảnh

Hãy nhớ sử dụng kích thước hình ảnh phù hợp với nhu cầu của bạn. Bạn nên tránh tải hình ảnh có độ phân giải cao và kích thước lớn vào một vùng chứa nhỏ (chẳng hạn như hình thu nhỏ). Thay vào đó, hãy sử dụng phương pháp giảm kích thước để giảm kích thước hình ảnh trước khi giải mã hình ảnh vào bộ nhớ.

Giảm mẫu phía máy khách

Các thư viện tải hình ảnh như Coil và Glide sẽ tự động xử lý việc giảm tần số lấy mẫu cho bạn. Bạn có thể định cấu hình các chiến lược giảm mẫu của chúng bằng cách sử dụng ImageLoader (đối với Coil) hoặc DownsampleStrategy (đối với Glide). Nếu đang quản lý bitmap theo cách thủ công, bạn có thể dùng inSampleSize để giải mã một phiên bản nhỏ hơn. Để thực hiện việc này một cách an toàn, trước tiên, bạn nên đặt inJustDecodeBounds thành true để đọc kích thước hình ảnh mà không cần phân bổ bộ nhớ, tính toán kích thước mẫu, đặt inSampleSize thành giá trị đó, đặt inJustDecodeBounds thành false, rồi giải mã hình ảnh.

Ưu tiên việc đổi kích thước phía máy chủ

Nếu có thể, hãy yêu cầu kích thước chính xác của hình ảnh mà bạn cần ngay từ máy chủ phụ trợ. Điều này giúp giảm mức sử dụng mạng và dung lượng bộ nhớ đệm của ổ đĩa, đồng thời giảm mức sử dụng bộ nhớ bằng cách tránh hao tổn bộ nhớ khi đổi kích thước hình ảnh trên thiết bị.

Bạn có thể định cấu hình các thư viện để tự động thêm kích thước khung hiển thị mục tiêu vào URL hình ảnh. Ví dụ: Coil cho phép thực hiện việc này bằng cách sử dụng các trình chặn tuỳ chỉnh và Glide hỗ trợ việc này bằng cách sử dụng các trình tải mô hình tuỳ chỉnh (chẳng hạn như BaseGlideUrlLoader).

Tránh kích thước bố cục không bị hạn chế

Để các trình tải hình ảnh giảm mẫu (phía máy khách hoặc phía máy chủ) một cách hiệu quả, chúng phải biết kích thước mục tiêu trước khi thực thi yêu cầu.

Tránh sử dụng wrapContentSize hoặc để kích thước không bị hạn chế trên các thành phần kết hợp tải hình ảnh từ xa. Nếu không suy luận được ranh giới mục tiêu, các thư viện này sẽ quay lại tải hình ảnh gốc có kích thước đầy đủ. Điều này có thể dẫn đến việc tải một hình ảnh lớn hơn đáng kể so với mức cần thiết, làm tăng mức sử dụng bộ nhớ và độ trễ.

Thay vào đó, hãy đặt kích thước rõ ràng cho thành phần kết hợp hình ảnh (ví dụ: bằng cách sử dụng Modifier.size) hoặc xác định tỷ lệ khung hình. Điều này cho phép công cụ bố cục tính toán chính xác mục tiêu pixel trước, sau đó trình tải hình ảnh có thể dùng mục tiêu này để yêu cầu và giải mã tài sản có kích thước chính xác.

Cung cấp tài nguyên thay thế cho nhiều kích thước màn hình

Nếu bạn đang vận chuyển hình ảnh bằng ứng dụng, hãy cân nhắc cung cấp các thành phần có kích thước khác nhau cho các độ phân giải thiết bị khác nhau. Điều này có thể giúp giảm dung lượng tải xuống của ứng dụng trên các thiết bị và cải thiện hiệu suất vì ứng dụng này sẽ tải hình ảnh có độ phân giải thấp hơn trên thiết bị có độ phân giải thấp hơn. Để biết thêm thông tin về cách cung cấp bitmap thay thế cho các kích thước thiết bị khác nhau, hãy xem tài liệu bitmap thay thế.

Không áp dụng khoảng đệm trực tiếp

Đôi khi, bạn có thể cần thêm khoảng đệm vào hình ảnh. Ví dụ: bạn có thể muốn hình ảnh được bao quanh bởi một đường viền trong suốt để tạo hiệu ứng hòm thư. Trong những trường hợp đó, đừng thêm khoảng đệm trực tiếp vào hình ảnh, thay đổi kích thước của hình ảnh. Thay vào đó, hãy giữ nguyên kích thước của hình ảnh và điều chỉnh vị trí của hình ảnh trên màn hình bằng cách sử dụng InsetDrawable. Ngoài ra, bạn có thể thêm khoảng đệm vào Thành phần kết hợp hoặc Khung hiển thị chứa hình ảnh.

Chọn định dạng pixel phù hợp

Cân bằng bộ nhớ và chất lượng bằng cách chọn định dạng pixel phù hợp. Sử dụng RGB_565 khi bạn không cần độ trong suốt; định dạng này sử dụng một nửa bộ nhớ của định dạng ARGB_8888 mặc định.

Trong Glide, bạn có thể định cấu hình chế độ này bằng cách sử dụng DecodeFormat. Trong Coil, bạn có thể sử dụng thuộc tính bitmapConfig.

Sử dụng vectơ khi có thể

Đối với hình ảnh được tạo thành từ các hình dạng hình học, đồ họa vectơ nhỏ hơn nhiều so với bitmap và có thể điều chỉnh tỷ lệ một cách mượt mà cho mọi mật độ hiển thị. Khi thích hợp, hãy dùng các phần tử như ShapeDrawable để biểu thị đồ hoạ.

Phát hành và sử dụng lại bitmap khi có thể

Các tệp đồ hoạ lớn có thể chiếm nhiều bộ nhớ. Để giảm tác động của các đối tượng này, bạn nên giải phóng hoặc sử dụng lại các đối tượng đồ hoạ bất cứ khi nào có thể.

Nếu bạn sử dụng một thư viện tải hình ảnh, hãy nhớ phát hành các bitmap vào nhóm được quản lý của thư viện khi bạn không cần chúng nữa. Thư viện có thể dùng lại các đối tượng khi cần và giữ sẵn một vùng đệm bộ nhớ cho các nhu cầu trong tương lai.

Nếu đang quản lý đồ hoạ theo cách thủ công, bạn nên giải phóng bitmap khi hoàn tất bằng cách gọi Bitmap.recycle và loại bỏ ngay tham chiếu Bitmap thay vì dựa vào quá trình thu gom rác.

Các mẹo và thủ thuật khác

Phần này liệt kê một số cách khác để cải thiện hiệu suất của ứng dụng khi xử lý đồ hoạ.

Không đóng gói hình ảnh lớn bằng tệp AAB/APK

Một trong những nguyên nhân hàng đầu dẫn đến dung lượng tải xuống ứng dụng lớn là do đồ hoạ được đóng gói trong tệp AAB hoặc APK. Hãy sử dụng công cụ phân tích APK để đảm bảo rằng bạn không đóng gói tệp hình ảnh lớn hơn yêu cầu. Hãy giảm kích thước hoặc cân nhắc đặt hình ảnh trên máy chủ và chỉ tải hình ảnh xuống khi được yêu cầu.

Tìm bitmap dư thừa

Nếu bạn có nhiều bản sao của cùng một hình ảnh, thì điều đó sẽ lãng phí bộ nhớ. Bạn có thể dùng trình phân tích tài nguyên của Android Studio để xác định các đồ hoạ dư thừa. Sử dụng trình phân tích tệp báo lỗi để ghi tệp báo lỗi và lọc kết quả bằng cách chọn chế độ cài đặt bitmap trùng lặp.

Khi sử dụng ImageBitmap, hãy gọi prepareToDraw trước khi vẽ

Khi sử dụng ImageBitmap, để bắt đầu quá trình tải hoạ tiết lên GPU, hãy gọi ImageBitmap#prepareToDraw() trước khi thực sự vẽ hoạ tiết đó. Điều này giúp GPU chuẩn bị hoạ tiết và cải thiện hiệu suất hiển thị hình ảnh trên màn hình. Hầu hết các thư viện tải hình ảnh đều đã thực hiện việc tối ưu hoá này, nhưng nếu bạn đang tự làm việc với lớp ImageBitmap, thì đây là điều cần lưu ý.

Ưu tiên truyền Int DrawableRes hoặc URL dưới dạng tham số vào thành phần kết hợp thay vì Painter

Do sự phức tạp trong việc xử lý hình ảnh (ví dụ: viết một hàm bằng cho Bitmaps sẽ tốn nhiều tài nguyên điện toán), Painter API rõ ràng không được đánh dấu là ổn định bằng chú thích @Stable. Các lớp không ổn định có thể dẫn đến các bản tái cấu trúc không cần thiết vì trình biên dịch không thể dễ dàng suy luận nếu dữ liệu thay đổi.

Do đó, bạn nên truyền URL hoặc mã tài nguyên drawable dưới dạng tham số cho composable, thay vì truyền Painter dưới dạng tham số.

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}