建议避免位图内存不足错误

时间:2013-01-09 12:32:25

标签: android bitmap heap out-of-memory

我正在开发一个Android应用程序。该应用程序有一个包含大量图像的视图。我有一个错误,我会尝试提供尽可能多的信息,希望有人能给我一些建议。

该应用程序在所有本地测试中都运行良好。但是,我收到了很多来自用户的崩溃:java.lang.OutOfMemoryError: bitmap size exceeds VM budget

这是堆栈跟踪

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)

我最大的问题是,即使在旧设备上,我也无法在本地重现此问题。

我已经实施了很多东西来尝试解决这个问题:

  1. 没有内存泄漏:我确保根本没有内存泄漏。当我不需要它时,我删除了视图。我还回收了所有的位图,并确保垃圾收集器正常工作。我在onDestroy()方法
  2. 中实施了所有必要的步骤
  3. 正确缩放图像尺寸:在获取图片之前,我会获得其尺寸并计算inSampleSize
  4. 堆大小:我还会在获取图像之前检测最大堆大小,并确保有足够的空间。如果没有足够的,我会相应地重新缩放图像。
  5. 计算正确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;
       }
    

    获取位图的代码

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

    根据旧设备的堆大小计算图片大小的代码

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

    我认为问题出在Heap上,有时os可能不允许应用程序使用maxheapsize。另外我最大的问题是我无法重现这个问题,所以当我尝试修复时,我必须等一下,看看用户是否仍然收到错误。

    我还可以尝试避免内存不足问题?任何建议将不胜感激。非常感谢

6 个答案:

答案 0 :(得分:8)

只需使用此功能进行解码...这是您错误的完美解决方案。因为我也得到了相同的错误,我得到了这个解决方案..

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

答案 1 :(得分:5)

通过缩小/缩放图像的大小,您可以摆脱内存不足异常, 试试这个

  BitmapFactory.Options options = new BitmapFactory.Options();
  options.inSampleSize = 6; 
  Bitmap receipt = BitmapFactory.decodeFile(photo.toString(),options);  //From File You can customise on your needs. 

答案 2 :(得分:4)

您好,您必须解码该文件。为此尝试使用以下方法。

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

    }

答案 3 :(得分:0)

我在另一个StackOverFlow问题中写了一个建议摘要:Android: BitmapFactory.decodeStream() out of memory with a 400KB file with 2MB free heap

答案 4 :(得分:0)

实际上问题在于开发os。在Android不像iOS,谷歌人根据相机分辨率开发此功能。位图占用大量内存,特别是对于像照片这样的丰富图像。不同的相机捕获具有不同像素的图像(不同的移动设备具有不同的相机像素容量)。在android中基于该像素,只有捕获的图像才会占用内存。所以很明显高分辨率图像不会被具有低像素容量的手机上传。 在android os中为每个应用程序分配最多16MB。如果上传的图像超过此值,那么java.lang.OutofMemoryError:位图大小将超出VM预算并且应用程序崩溃。 参考这个 http://developer.android.com/training/displaying-bitmaps/index.html

答案 5 :(得分:0)

如果你想避免使用OOM,你可以捕获OOM并增加sampleSize直到可以解析图像:

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

希望它有所帮助。

不适合捕捉错误,只是解决方法。