37

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:

  1. 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
  2. Image size scaled correctly: Before getting the image I get its dimension and calculate the inSampleSize.
  3. 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

trincot
  • 317,000
  • 35
  • 244
  • 286
Y2theZ
  • 10,162
  • 38
  • 131
  • 200
  • take one very big image and try to use it with your code. I think it will crash ) I dont like this line: Bitmap bmp = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), Uri.fromFile(file)); – Leonidos Jan 09 '13 at 12:42
  • I wrote a summary of suggestions in another question: http://stackoverflow.com/questions/11820266/android-bitmapfactory-decodestream-out-of-memory-with-a-400kb-file-with-2mb-f/16528487#16528487 – Paulo Cheque May 13 '13 at 18:28
  • @Youssef, I think you should take a look at this - http://stackoverflow.com/a/15380872/1433187 I was getting out of memory error, then this solution worked perfectly for me. – Khobaib Jul 18 '13 at 09:29
  • Have you found a proper solution to this question ? – SteBra May 27 '14 at 11:19
  • 1
    @Stebra no I did not find a proper solution for this. but recently I replaced my code above with the code on the official android tutorial. http://developer.android.com/training/displaying-bitmaps/index.html check that link they have an awesome sample that you can download. I found it better than what I had but still getting out of memory errors. – Y2theZ May 27 '14 at 17:54

6 Answers6

8

just use this function to decode...this is perfect solution for your error..because i also getting same error and i got this solution..

public static Bitmap decodeFile(File f,int WIDTH,int HIGHT){
     try {
         //Decode image size
         BitmapFactory.Options o = new BitmapFactory.Options();
         o.inJustDecodeBounds = true;
         BitmapFactory.decodeStream(new FileInputStream(f),null,o);

         //The new size we want to scale to
         final int REQUIRED_WIDTH=WIDTH;
         final int REQUIRED_HIGHT=HIGHT;
         //Find the correct scale value. It should be the power of 2.
         int scale=1;
         while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT)
             scale*=2;

         //Decode with inSampleSize
         BitmapFactory.Options o2 = new BitmapFactory.Options();
         o2.inSampleSize=scale;
         return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
     } catch (FileNotFoundException e) {}
     return null;
 }
Mehul Ranpara
  • 4,245
  • 2
  • 26
  • 39
  • 3
    Hi, thank you for your suggestion but I am actually doing the same thing but with more advanced check. I had the exact code you have but was having a crash. so I modified it to take in consideration more advanced stuff. But the logic in your code is present in mine. Thanks – Y2theZ Jan 09 '13 at 12:51
  • have you used this function? – Mehul Ranpara Jan 09 '13 at 13:11
  • yes I used it first. but I got the OutOfMemoryError so I modified it to what I have today. it reduced the error but it is still appearing – Y2theZ Jan 09 '13 at 13:13
  • what is the size of your image ? – Mehul Ranpara Jan 09 '13 at 13:17
  • usually it is 320x320. but the problem is not with the size of the image because I am scalling it to a very low resolution if needed (When calculating the available heap size) – Y2theZ Jan 09 '13 at 13:35
  • 1
    why not to use just `o2.inSampleSize = Math.max(o.outHeight,o.outWidth)/imageMaxSize;` instead of `while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT) scale*=2;` According to documentation "Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2." so android will do the rounding automatically. – Roman Nazarevych Oct 09 '15 at 15:29
4

By Reducing/Scale size of the Image you can get rid out of the Out of Memory Exception, Try this

  BitmapFactory.Options options = new BitmapFactory.Options();
  options.inSampleSize = 6; 
  Bitmap receipt = BitmapFactory.decodeFile(photo.toString(),options);  //From File You can customise on your needs. 
vinothp
  • 9,939
  • 19
  • 61
  • 103
4

Hi you have to decode the file . for this try with the following method.

  public static Bitmap new_decode(File f) {

        // decode image size

        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        o.inDither = false; // Disable Dithering mode

        o.inPurgeable = true; // Tell to gc that whether it needs free memory,
                                // the Bitmap can be cleared

        o.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the future
        try {
            BitmapFactory.decodeStream(new FileInputStream(f), null, o);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        // Find the correct scale value. It should be the power of 2.
        final int REQUIRED_SIZE = 300;
        int width_tmp = o.outWidth, height_tmp = o.outHeight;
        int scale = 1;
        while (true) {
            if (width_tmp / 1.5 < REQUIRED_SIZE && height_tmp / 1.5 < REQUIRED_SIZE)
                break;
            width_tmp /= 1.5;
            height_tmp /= 1.5;
            scale *= 1.5;
        }

        // decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        // o2.inSampleSize=scale;
        o.inDither = false; // Disable Dithering mode

        o.inPurgeable = true; // Tell to gc that whether it needs free memory,
                                // the Bitmap can be cleared

        o.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the future
        // return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        try {

//          return BitmapFactory.decodeStream(new FileInputStream(f), null,
//                  null);
            Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(f), null, null);
            System.out.println(" IW " + width_tmp);
            System.out.println("IHH " + height_tmp);           
               int iW = width_tmp;
                int iH = height_tmp;

               return Bitmap.createScaledBitmap(bitmap, iW, iH, true);

        } catch (OutOfMemoryError e) {
            // TODO: handle exception
            e.printStackTrace();
            // clearCache();

            // System.out.println("bitmap creating success");
            System.gc();
            return null;
            // System.runFinalization();
            // Runtime.getRuntime().gc();
            // System.gc();
            // decodeFile(f);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }

    }
itsrajesh4uguys
  • 4,610
  • 3
  • 20
  • 31
  • 1
    I don't like the System.gc() solution. try to find why it's happening, look for memory leak and so one. – Avi Levin Oct 09 '15 at 15:15
0

I wrote a summary of suggestions in another StackOverFlow question: Android: BitmapFactory.decodeStream() out of memory with a 400KB file with 2MB free heap

Community
  • 1
  • 1
Paulo Cheque
  • 2,797
  • 21
  • 22
0

actually the problem is with the development os. In android unlike iOS , google people develop this based on camera resolution. Bitmaps take up a lot of memory, especially for rich images like photographs.Different cameras captures images with different pixels(different mobiles have different camera pixel capacity). Here in android based on that pixels only the captured image will take memory. so obviously a high resolution image will not uploaded by a phone with low pixel capacity. In android os allocates utmost 16MB to every application. If the uploaded image takes more than this then java.lang.OutofMemoryError: bitmap size exceeds VM budget will occur and application crashes. refer this http://developer.android.com/training/displaying-bitmaps/index.html

GvSharma
  • 2,632
  • 1
  • 24
  • 30
0

If u want to avoid OOM, u can catch OOM and increase the sampleSize until the image can be resolved:

private Bitmap getBitmapSafely(Resources res, int id, int sampleSize) {
// res = context.getResources(), id = R.drawable.yourimageid
    Bitmap bitmap = null;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPurgeable = true;
    options.inSampleSize = sampleSize;
    try {
          bitmap = BitmapFactory.decodeResource(res,
                      id, options);
    } catch (OutOfMemoryError oom) {
        Log.w("ImageView", "OOM with sampleSize " + sampleSize, oom);
        System.gc();
        bitmap = getBitmapSafely(res, id, sampleSize + 1);
    }

    return bitmap;
}

Hope it helps.

It is not suitable to catch the Error, just a workaround.

Euporie
  • 1,896
  • 1
  • 21
  • 23
  • 5
    An `OutOfMemoryError` cannot be caught – Melllvar Oct 19 '14 at 06:25
  • @Melllvar I tested these code and I have really caught the OOM. Do you mean is not suitable to catch the OOM? – Euporie Oct 20 '14 at 02:03
  • OK, so I'm seeing a lot of conflicting information (e.g. see [this post](http://stackoverflow.com/questions/14812508/java-lang-outofmemoryerror-even-in-a-try-catch-block)). I myself have had unrecoverable crashes inside `try/catch(OutOfMemoryError)`, but what I'm seeing suggests that this is more or less a matter of luck (e.g. it may be possible, but if you do recover from an error, there is a chance that the error will leave the program in an indeterminate state; therefore the crash will occur no matter what). In any case, your solution may work (I stand corrected) - just not always. – Melllvar Oct 20 '14 at 02:15
  • @Melllvar thanks, I need to learn more about the differences betweeen exception and error, and I have edited my post. – Euporie Oct 20 '14 at 02:46
  • 1
    `OutOfMemory` *most of time* never gets `catch`ed. Just crashes the app. – FindOut_Quran Nov 01 '15 at 03:00