How to load large images in Android and avoiding the out of memory error?

I'm working on an app that uses large images (1390 × 870 : 150kb - 50kb). I'm adding images as I tap a trigger/ImageView.

At a certain point I'm getting an out of memory error:

java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)

To resize the image I'm doing this:

Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
     imageStream = new FileInputStream(imageLoc);
     productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);

     productIV.setImageBitmap(productIndex);
     } catch (FileNotFoundException e1) {
          // TODO Auto-generated catch block
          e1.printStackTrace();
     }
}


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

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

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

// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId, options);
}

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 / 3;
    final int halfWidth = width / 3;

    // 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;
}

I got this way of resizing to save space from the Android Docs: Loading Large Bitmaps Efficiently

According to the log this like is the culprit in the decodeSampledBitmapFromResource method :

return BitmapFactory.decodeFile(resId, options);

----- edit ----- Here is how I'm adding each item to the FrameLayout.

for(int ps=0;ps<productSplit.size();ps++){
    //split each product by the equals sign
    List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));

    String tempCarID = productItem.get(0);
    tempCarID = tempCarID.replace(" ", "");
    if(String.valueOf(carID).equals(tempCarID)){

        ImageView productIV = new ImageView(Configurator.this);
        LayoutParams productParams = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        productIV.setId(Integer.parseInt(partIdsList.get(x)));
        productIV.setLayoutParams(productParams);

        final String imageLoc = productItem.get(2);

        InputStream imageStream;
        try {
            imageStream = new FileInputStream(imageLoc);
            productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
            productIV.setImageBitmap(productIndex);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        productLayers.addView(productIV);

    }
}

Solution 1:

You can use another bitmap-config to heavily decrease the size of the images. The default is RGB-config ARGB8888 which means four 8-bit channels are used (red, green, blue, alhpa). Alpha is transparency of the bitmap. This occupy a lot of memory - imagesize X 4. So if the imagesize is 4 megapixel 16 megabytes will immidiately be allocated on the heap - quickly exhausting the memory.

Instead - use RGB_565 which to some extent deteriorate the quality - but to compensate this you can dither the images.

So - to your method decodeSampledBitmapFromResource - add the following snippets:

 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;

In your code:

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

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

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

 // Decode bitmap with inSampleSize set
 options.inJustDecodeBounds = false;
 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;
 return BitmapFactory.decodeFile(resId, options);
 }

References:

http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888

Solution 2:

High resolution devices such as S4 usually run out of memory if you do not have your image in the proper folder which is drawable-xxhdpi. You can also put your image into drawable-nodpi. The reason it would run out of memorey if your image just in drawable that the android would scale the image thinking that the image was designed for low resolution.