[Android 04] OutOfMemoryException

1. Nó là gì?
OutOfMemory là một ngoại lệ xảy ra khi runtime, nguyên nhân từ việc quá tải trong việc yêu cầu cấp thêm vùng nhớ trong heap của Java Virtual Machine.
Ngoại lệ này thường xảy ra khi load một lượng lớn dung lượng bitmap để cấp vào 1 object ImageView.
2. Cách giải quyết hiệu quả
Có nhiều cách giải quyết vấn đề này, sau đây là 3 cách làm thường gặp và mang lại hiệu quả cao nhất.
- Cách đầu tiên nhìn có vẻ rất hiệu quả khi build ứng dụng, đó là thêm largeHeap vào trong AndroidManifest, việc này nhằm yêu cầu mở rộng cấp thêm bộ nhớ cho riêng ứng dụng.
 android:largeHeap="true"
Nhìn chung nó làm việc hiệu quả, nhưng không giải quyết cốt lõi vấn đề.

- Cách thứ hai có vẻ ổn hơn: Hãy giải phóng resource sau khi đã load bitmap, giải phóng vùng nhớ.

if (bmap!=null && !bmap.isRecycled){
   bmap.recycle();
}
- Cách thứ 3: Tải phiên bản đã scale vào vùng nhớ:
Khi đã biết được các kích thước của hình ảnh, chúng ta phải quyết định liệu rằng full-size của hình ảnh được load vào vùng nhớ, hay là chỉ 1 phần của nó, sau đây là một số yếu tố có thể suy xét:
  • Tính toán dung lượng bộ nhớ sử dụng cho việc tải full hình ảnh vào vùng nhớ.
  • Tính toán vùng nhớ sẽ sẵn sàng commit để tải hình ảnh, được yêu cầu cấp phát từ ứng dụng, nghĩa là tính toán khối lượng vùng nhớ tổng của cả ứng dụng.
  • Tính toán kích thước của object ImageView hoặc UI, cái mà để load bitmap hay base64 vào trong đó, tức là tính toán giỏ đựng hàng thay vì tính toán hàng phải mua.
  • Tính toán kích cỡ và tỷ lệ của thiết bị, cái này cũng quan trọng không kém, ví dụ như việc tải 1024*768 pixel hình ảnh vào vùng nhớ thì thay vào đó, có thể hiển thị nó ở dạng thumbnail 128*96 pixel vào ImageView. Có thể ví dụ cho dễ hiểu về phương pháp này, để tải 1 phiên bản nhỏ hơn vào trong bộ nhớ, thiết đặt inSampleSize thành true vào trong BitmapFactory.Options. Với một hình ảnh có độ phân giải 2048x1536, được giải mã với inSampleSize với 4 tiến trình, 1 bitmap còn lại xấp xỉ 512x384, và dung lượng vùng nhớ cần chỉ là 0.75MB, thay vì 12MB(giả sử cấu hình của bitmap là ARGB_8888). Dưới đây là tham khảo của https://developer.android.com/topic/performance/graphics/load-bitmap.html#load-bitmap

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;
}
Để sử dụng hàm tính toán trên, giải mã đầu tiên là thiết đặt inJustDecodeBounds là true, 
ném điều kiện vào và giải mã lại sử dụng inSampleSize và inJustDecodeBounds là false, như
 sau:


public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}
Để làm cho phương thức trên được dễ dàng sử dụng, truyền vào params, ví dụ 200x200 pixel, ta làm như sau:
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 200, 200));

Nhận xét

Bài đăng phổ biến từ blog này

[Android-02] LayoutParams trong Android