I am working on an android application. The application has a view containing lots of image. I had an error, I will try to give as much information as possible hoping someone can give me some suggestions.
The application was working great on all the local testings. However, I received lots of crashes from users: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
This is the stack trace
0 java.lang.OutOfMemoryError: bitmap size exceeds VM budget 1 at android.graphics.Bitmap.nativeCreate(Native Method) 2 at android.graphics.Bitmap.createBitmap(Bitmap.java:507) 3 at android.graphics.Bitmap.createBitmap(Bitmap.java:474) 4 at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:379) 5 at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498) 6 at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473) 7 at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336) 8 at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:359) 9 at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:385) My biggest problem is that I was not able to reproduce the issue locally even on old devices.
I have implemented lots of things to try to resolve this:
- No memory leaks: I made sure there is no memory leaks at all. I removed the views when I dont need them. I also recycled all the bitmaps and made sure the garbage collector is working as it should. And I implemented all the necessary steps in the
onDestroy()method - Image size scaled correctly: Before getting the image I get its dimension and calculate the
inSampleSize. - Heap size: I also detect the Max Heap size before getting the image and make sure there is enough space. If there is not enough I rescale the image accordingly.
Code to calculate the correct inSampleSize
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) { if(width > height) { inSampleSize = Math.round((float) height / (float) reqHeight); } else { inSampleSize = Math.round((float) width / (float) reqWidth); } } return inSampleSize; } Code to get the bitmap
// decodes image and scales it to reduce memory consumption private static Bitmap decodeFile(File file, int newWidth, int newHeight) {// target size try { Bitmap bmp = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), Uri.fromFile(file)); if(bmp == null) { // avoid concurrence // Decode image size BitmapFactory.Options option = new BitmapFactory.Options(); // option = getBitmapOutput(file); option.inDensity = res.getDisplayMetrics().densityDpi < DisplayMetrics.DENSITY_HIGH ? 120 : 240; option.inTargetDensity = res.getDisplayMetrics().densityDpi; if(newHeight > 0 && newWidth > 0) option.inSampleSize = calculateInSampleSize(option, newWidth, newWidth); option.inJustDecodeBounds = false; byte[] decodeBuffer = new byte[12 * 1024]; option.inTempStorage = decodeBuffer; option.inPurgeable = true; option.inInputShareable = true; option.inScaled = true; bmp = BitmapFactory.decodeStream(new FileInputStream(file), null, option); if(bmp == null) { return null; } } else { int inDensity = res.getDisplayMetrics().densityDpi < DisplayMetrics.DENSITY_HIGH ? 120 : 240; int inTargetDensity = res.getDisplayMetrics().densityDpi; if(inDensity != inTargetDensity) { int newBmpWidth = (bmp.getWidth() * inTargetDensity) / inDensity; int newBmpHeight = (bmp.getHeight() * inTargetDensity) / inDensity; bmp = Bitmap.createScaledBitmap(bmp, newBmpWidth, newBmpHeight, true); } } return bmp; } catch(Exception e) { Log.e("Error calling Application.decodeFile Method params: " + Arrays.toString(new Object[]{file }), e); } return null; } Code to calculate image size based on Heap size for older devices
private void calculateImagesSize() { // only for android older than HoneyComb that does not support large heap if(Build.VERSION.SDK_INT < Constants.HONEYCOMB) { long maxHeapSize = Runtime.getRuntime().maxMemory(); long maxImageHeap = maxHeapSize - 10485760; if(Application.getResource().getDisplayMetrics().densityDpi >= DisplayMetrics.DENSITY_XHIGH) { maxImageHeap -= 12 * 1048576; } if(maxImageHeap < (30 * 1048576)) { int screenHeight = Math.min(Application.getResource().getDisplayMetrics().heightPixels, Application.getResource() .getDisplayMetrics().widthPixels); long maxImageSize = maxImageHeap / 100; long maxPixels = maxImageSize / 4; long maxHeight = (long) Math.sqrt(maxPixels / 1.5); if(maxHeight < screenHeight) { drawableHeight = (int) maxHeight; drawableWidth = (int) (drawableHeight * 1.5); } } } } I think the problem is with the Heap, maybe sometimes the os doesn't allow the application to use the maxheapsize. Also my biggest problem is that I was not able to reproduce the issue, so when I try a fix I have to wait a little to see if users are still getting the error.
What more could I try to avoid Out of memory issues? Any suggestions would be greatly appreciated. Thanks a lot