Loading Large Bitmaps Efficiently
Bài đăng này đã không được cập nhật trong 3 năm
Bạn có bao giờ tự hỏi, một ảnh với dung lượng 100mb thì nó được lưu vào memory dung lượng bao nhiêu? Làm thế nào mà nó bị OOM vậy? Ở đây chắc ai code Android cũng từng gặp vấn đề về dung lượng, độ phân giải ảnh khi load vào memory. Điều đầu tiên mình muốn nhấn mạnh là mình khuyên các bạn nên sử dụng Glide, Picasso hoặc Fresco để load ảnh, đó là những thư viện đã được Google hay Facebook khuyên nên dùng. Nếu muốn tìm hiểu thì dành chút thời gian đọc bài này nhé, mình muốn chia sẻ một ít thông tin mình biết về việc loading một ảnh lớn và thực sự công việc bên trong của nó là như thế nào?
Load Bitmap Into Memory
Rất đơn giản, bạn chỉ cần decode ảnh của mình, sử dụng BitmapFactory
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage);
imageView.setImageBitmap(bitmap);
Bây giờ bạn hãy kiểm tra bitmap size trong memory. bitmap.getByteCount()
trả về size là 12262248 Bytes = 12.3 Mb
Nhìn vào con số kia bạn có thể bị nhầm lẫn, vì bạn nghĩ ảnh chỉ có size khoảng 3.5 Mb thôi, vậy tại sao lại có số kia, nguyên nhân là :
Hình ảnh được nén khi ở trên ổ cứng, dưới định dạng JPG, PNG hay một số các định dạng tương tự. Khi bạn lưu vào trong bộ nhớ máy, nó không bị nén nữa và cần rất nhiều bộ nhớ để có thể lưu được hết các điểm ảnh.
Steps:
- Get size của hình ảnh không load vào memory
- Tính tỷ lệ với kích thước của ảnh
- Load bitmap vào memory với các kích thước đã tính
BitmapFactory.Options
Dùng class này để thực hiệp step 1
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options);
Bạn có thể nhìn thấy khi set options.inJustDecodeBounds = true;
, điều này có nghĩa là mình không muốn load bitmap vào memory. Chúng ta chỉ cần get thông tin width, height, ... của ảnh.
Chúng ta có thể tính toán được với các thông tin nhận được
options.outHeight : 1126
options.outWidth : 2000
options.bitmap : null
Reducing Image Size (In Memory)
Trong BitmapFactory.Options
có giá trị inSampleSize
, nếu bạn có ảnh 1000x1000, và bạn set inSampleSize
= 2 trước khi decode, bạn sẽ có ảnh 500x500 sau khi decode.
Tương tự vậy nếu ảnh trước khi decode là 200x400 và set inSampleSize
= 5 thì sau khi decode sẽ có ảnh 40x80
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = 3;
BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options);
Đoạn code trên set cứng giá trị là 3, nhưng giá trị của inSampleSize
sẽ cần tính động theo kích thước của hình ảnh hiển thị, nó phụ thuộc vào bạn, nghĩa là bạn có thể viết thuật toán để tính nếu cần thiết.
Trong document của android, giá trị này được tính dựa trên 2 giá trị reqWidth
và reqHeight
:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Và chúng ta sẽ sử dụng hàm tính toán này :
options.inSampleSize = calculateInSampleSize(options, 500,500);
options.inJustDecodeBounds = false;
Bitmap smallBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options);
Ta có thể thấy, inJustDecodeBounds
đã được chuyển thành false
và bitmap được decode với biến options
Bây giờ bitmap.getByteCount()
sẽ trả về 3.1Mb, đó chính là memory size. Như mình nói ở trên, anh được nén khi lưu trong ổ cứng, nó lơn hơn nhiều khi ta load vào trong memory.
Vậy ta đã giảm từ 12.3 MB xuống 3.1MB, tương đương với 75% trong memory, wow
Reducing Image Size (In Disk)
Bước tiếp theo, ta cần giảm size ảnh trong ổ cứng, chúng ta có thể dùng method compress
của Bitmap, về chất lượng của ảnh, giá trị 100
nghĩa là chất lượng tương đương nhau.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
byte[] bitmapdata = bos.toByteArray();
Khi ta tính kích thước thật của ảnh là 1.6MB, bây giờ thử thay đổi chất lượng ảnh và kiểm tra lại kích thước xem sao
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, bos);
Tôi đã thay chất lượng ảnh từ 100
về 50
và kết quả là 24.4 KB
. Cần đổi định dạng ảnh về .JPEG
nếu muốn thay đổi chất lượng của bitmap, ở định PNG
ta không thể thay đổi được chất lượng của ảnh.
Chúng ta đã giảm size của file từ 1.6MB về 24.4KB
Đây là kết quả :
Nguồn:
https://developer.android.com/topic/performance/graphics/load-bitmap.html
https://android.jlelse.eu/loading-large-bitmaps-efficiently-in-android-66826cd4ad53
All rights reserved