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 đồ hoạ nhỏ ở định dạng nén như JPG hoặc PNG cũng có thể biế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ớ, gây ả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 quả của ứng dụng bằng cách sử dụng các thư viện tải hình ảnh như Coil (cho các dự án ưu tiên Kotlin) hoặc Glide (cho các dự án Java). Các 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 việc như lưu hình ảnh vào bộ nhớ đệm, giảm mẫu đồ hoạ khi cần và tái sử dụng các đối tượng đồ hoạ.

Giảm mẫu 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 lớn có độ phân giải cao 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 tính năng giảm mẫu để giảm tỷ lệ 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 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 (cho Coil) hoặc DownsampleStrategy (cho Glide). Nếu đang quản lý bitmap theo cách thủ công, bạn có thể sử dụng inSampleSize để giải mã phiên bản nhỏ hơn. Để thực hiện việc này một cách an toàn, bạn nên đặt inJustDecodeBounds thành true trước tiên để đọ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, sau đó giải mã hình ảnh.

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

Nếu có thể, hãy yêu cầu kích thước hình ảnh chính xác mà bạn cần trực tiếp 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 trình chặn tuỳ chỉnh và Glide hỗ trợ việc này bằng cách sử dụng 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ị ràng buộ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ị ràng buộc trên các thành phần kết hợp tải hình ảnh từ xa. Nếu không thể suy ra giới hạn mục tiêu, các thư viện này sẽ quay lại tải hình ảnh có kích thước đầy đủ ban đầu. Điều này có thể dẫn đến việc tải 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 trên thành phần kết hợp hình ảnh (ví dụ: 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ể sử dụng để yêu cầu và giải mã thành phầ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 trực tiếp khoảng đệm

Đô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 letterbox. Trong những trường hợp đó, đừng thêm trực tiếp khoảng đệm vào hình ảnh, làm 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 việc 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ơ nếu có thể

Đối với hình ảnh được tạo 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 phù hợp, hãy sử dụng các phần tử như ShapeDrawable để biểu thị đồ hoạ.

Giải phóng và tái sử dụng 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 chúng, bạn nên giải phóng hoặc tái sử dụng các đối tượng đồ hoạ bất cứ khi nào có thể.

Nếu bạn sử dụng thư viện tải hình ảnh, hãy nhớ giải phóng bitmap vào nhóm được quản lý của thư viện khi bạn không còn cần chúng nữa. Thư viện có thể tái sử dụng các đối tượng khi cần và duy trì bộ đệ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 thập 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, điều đó sẽ lãng phí bộ nhớ. Bạn có thể sử dụng trình phân tích Android Studio để xác định đồ 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 duplicate bitmaps (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 chuyể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), API Painter 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 ra 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) {

}