【问题标题】:OutOfMemory exception when loading bitmap from external storage从外部存储加载位图时出现 OutOfMemory 异常
【发布时间】:2011-05-05 09:41:14
【问题描述】:

在我的应用程序中,我从 JPEG 和 PNG 文件中加载了几个图像。当我将所有这些文件放入 assets 目录并以这种方式加载时,一切正常:

InputStream stream = getAssets().open(path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);

但是当我尝试从 sd 卡加载完全相同的图像时,我得到了 OutOfMemory 异常!

InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);

这是我在日志中得到的:

11-05 00:53:31.003: ERROR/dalvikvm-heap(13183): 827200-byte external allocation too large for this process.
11-05 00:53:31.003: ERROR/GraphicsJNI(13183): VM won't let us allocate 827200 bytes
...
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-05 00:53:31.053: ERROR/AndroidRuntime(13183):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
...

为什么会发生这种情况?

更新:在真实设备上尝试了这两种方法 - 似乎我无法将超过 12MB 的位图加载到所谓的“外部存储器”(这不是 sd 卡)中。

【问题讨论】:

  • 你在什么条件下测试上面的代码?在模拟器中,或连接 USB 的真实设备中?可能是您的 USB 模式设置为锁定 SD 卡的磁盘模式。
  • 我在模拟器中运行这段代码。
  • jpg/png 文件的大小是多少?
  • 我加载的最大JPG文件大小为400KB,为800x600x24。
  • 你有 800x600 的屏幕吗?如果你不这样做,你应该对图像进行超采样以使用更少的内存。 stackoverflow.com/questions/477572/…

标签: android out-of-memory


【解决方案1】:

允许inSampleSize 调整最终读取图像的大小。 AssetFileDescriptor 的getLength() 允许获取文件大小。

您可以根据 getLength() 改变 inSampleSize 以防止 OutOfMemory 像这样:

private final int MAX_SIZE = 500000;

public Bitmap readBitmap(Uri selectedImage)
{
    Bitmap bm = null;
    AssetFileDescriptor fileDescriptor = null;
    try
    {
        fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,"r");
        long size = fileDescriptor.getLength();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = (int) (size / MAX_SIZE);
        bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    finally
    {
        try {
            if(fileDescriptor != null) fileDescriptor.close();
        } catch (IOException e) {}
    }
    return bm;
}

【讨论】:

    【解决方案2】:

    感谢所有线程,我找到了一个适用于真实设备的解决方案。 技巧都是关于使用的

    BitmapFactory.Options opts=new BitmapFactory.Options();
    opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger
    

    但对我来说,这还不够。我的原始图像(取自相机应用程序)是 3264x2448。对我来说正确的比率是 3,因为我想要一个 1024x768 的普通 VGA 图像。

    但是将 inSampleSize 设置为 3 是不够的:仍然是内存不足异常。 所以最后我选择了一种迭代方法:我从计算出的正确大小开始,并增加它直到我停止出现 OOM 异常。 对我来说,它是 4 个样本。

    // Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    // o2.inSampleSize = scale;
    float trueScale = o.outWidth / 1024;
    o2.inPurgeable = true;
    o2.inDither = false;
    Bitmap b = null;
    do {
         o2.inSampleSize = (int) trueScale;
         Log.d(TAG, "Scale is " + trueScale);
     try {
        b = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (OutOfMemoryError e) {
            Log.e(TAG,"Error decoding image at sampling "+trueScale+", resampling.."+e);
            System.gc();
        try {
            Thread.sleep(50);
         } catch (InterruptedException e1) { 
             e1.printStackTrace();
         }
    }
        trueScale += 1;
    } while (b==null && trueScale < 10);
    return b;
    

    【讨论】:

      【解决方案3】:
      The best solution i found and edited according to my need
      
      public static Bitmap getImageBitmap(String path) throws IOException{
              // Allocate files and objects outside of timingoops             
              File file = new File(thumbpath);        
              RandomAccessFile in = new RandomAccessFile(file, "rws");
              final FileChannel channel = in.getChannel();
              final int fileSize = (int)channel.size();
              final byte[] testBytes = new byte[fileSize];
              final ByteBuffer buff = ByteBuffer.allocate(fileSize);
              final byte[] buffArray = buff.array();
              @SuppressWarnings("unused")
              final int buffBase = buff.arrayOffset();
      
              // Read from channel into buffer, and batch read from buffer to byte array;
              long time1 = System.currentTimeMillis();
              channel.position(0);
              channel.read(buff);
              buff.flip();
              buff.get(testBytes);
              long time1 = System.currentTimeMillis();
              Bitmap bmp = Bitmap_process(buffArray);
              long time2 = System.currentTimeMillis();        
              System.out.println("Time taken to load: " + (time2 - time1) + "ms");
      
              return bmp;
          }
      
          public static Bitmap Bitmap_process(byte[] buffArray){
              BitmapFactory.Options options = new BitmapFactory.Options();
      
              options.inDither=false;                     //Disable Dithering mode
              options.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
              options.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
              options.inTempStorage=new byte[32 * 1024];  //Allocate some temporal memory for decoding
      
              options.inSampleSize=1;
      
              Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options);
              return imageBitmap;
          }
      

      【讨论】:

        【解决方案4】:

        使用以下代码,您将永远不会收到以下错误:java.lang.OutOfMemoryError: bitmap size超出VM预算

                      BitmapFactory.Options bounds = new BitmapFactory.Options();
        
                      bounds.inSampleSize = 4;
        
                      myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds);
        
                      picturesView.setImageBitmap(myBitmap);
        

        【讨论】:

          【解决方案5】:

          换一种方式试试……

          Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");
          

          【讨论】:

          • 试过了,结果和 InputStream: OutOfMemory 一样。
          【解决方案6】:

          我发现开发 Android 应用程序的最常见错误之一是“java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget”错误。我在更改方向后使用大量位图的活动上经常发现此错误:活动被销毁,再次创建,布局从 XML 中“膨胀”,消耗了位图可用的 VM 内存。

          前一个活动布局上的位图没有被垃圾收集器正确释放,因为它们交叉引用了它们的活动。经过多次实验,我找到了一个非常好的解决这个问题的方法。

          首先,在 XML 布局的父视图上设置“id”属性:

              <?xml version="1.0" encoding="utf-8"?>
              <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="fill_parent"
               android:layout_height="fill_parent"
               android:id="@+id/RootView"
               >
               ...
          

          然后,在 Activity 的 onDestroy() 方法上,调用 unbindDrawables() 方法,将引用传递给父 View,然后执行 System.gc()

              @Override
              protected void onDestroy() {
              super.onDestroy();
          
              unbindDrawables(findViewById(R.id.RootView));
              System.gc();
              }
          
              private void unbindDrawables(View view) {
                  if (view.getBackground() != null) {
                  view.getBackground().setCallback(null);
                  }
                  if (view instanceof ViewGroup) {
                      for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                      unbindDrawables(((ViewGroup) view).getChildAt(i));
                      }
                  ((ViewGroup) view).removeAllViews();
                  }
              }
          

          这个 unbindDrawables() 方法递归地探索视图树并且:

          1. 移除所有背景可绘制对象的回调
          2. 删除每个视图组中的子项

          【讨论】:

            【解决方案7】:

            我尝试了here & 在其他资源中提到的所有方法,但我推断将 ImageView 的引用设置为 null 将解决问题:

              public Bitmap getimage(String path ,ImageView iv)
               {
                //iv is passed to set it null to remove it from external memory
                iv=null;
                InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
                Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
                stream.close();
                stream=null;
                return bitmap;
                }
            

            &你完成了!

            注意:虽然它可以解决上述问题,但我建议你检查Tom van Zummeren的优化图像加载。

            还要检查SoftReference:所有指向软可访问对象的 SoftReference 都保证在 VM 抛出 OutOfMemoryError 之前被清除。

            【讨论】:

              【解决方案8】:

              这里有两个问题....

              • 位图内存不在 VM 堆中,而是在本机堆中 - 请参阅 BitmapFactory OOM driving me nuts
              • 本机堆的垃圾收集比 VM 堆更懒惰 - 因此每次执行 Activity 的 onPause 或 onDestroy 时,您都需要非常积极地执行 bitmap.recycle 和 bitmap =null

              【讨论】:

                【解决方案9】:

                这是我们所有人在从 sdcard 加载图像时都面临的一个相当普遍的问题。

                我发现的解决方案是在使用 decodeFileDescriptor 加载图像时首先使用 inJustDecodeBounds 。那实际上不会解码图像,而是给出图像大小。现在我可以适当地缩放它(使用选项),以便调整显示区域的图像大小。它是必需的,因为您的 5MP 图像可以轻松接管手机上的低内存。我认为这是最优雅的解决方案。

                【讨论】:

                • 我正在考虑,但这也意味着我必须为一张图片提出 2 次请求
                • 是的,但我没有遇到任何减速,因为两个调用可能是因为 inJustDecodeBounds 没有做很多工作
                【解决方案10】:

                你不能依赖 GC 来回收你的位图内存。 不需要时,必须明确回收位图。

                查看Bitmap方法:

                无效回收() 释放与此位图像素相关的内存,并将位图标记为“死”,这意味着如果调用 getPixels() 或 setPixels() 将抛出异常,并且不会绘制任何内容。

                【讨论】:

                • 我不想回收所有这些位图,我正在使用它们。
                • 如果一个位图对用户隐藏,你最好回收它们。如果它们再次显示,您将再次加载它们。手机的内存是有限的。
                • 当尝试加载已回收的位图时会抛出异常,提示无法加载回收的位图...
                【解决方案11】:

                您的 API 使用可能没有任何问题,我想我们所能做的就是推断使用 AssetManager 比从 SD 卡打开随机文件涉及的幕后堆分配更少。

                800KB 在任何人的书中都是一个严重的分配......这无疑将用于解压缩的图像像素。鉴于您知道图像的大小,它的深度是多少?如果是 32bpp,请尝试使用 inPreferredConfig 覆盖它。

                【讨论】:

                • 所以,emulator only 上的 AssetManager 似乎确实可以避免总加载的位图大小上限。 :(
                【解决方案12】:

                与其直接从 SD 卡加载,为什么不使用 getCacheDir() 将图像移动到手机内部存储中的缓存或使用临时目录来存储图像?

                请参阅thisthis 了解外部内存使用情况。此外,this article 可能与您有关。

                【讨论】:

                  【解决方案13】:
                  • 当大量使用位图时,不要调试应用程序 - 只需运行它。调试器会留下内存泄漏。
                  • 位图非常昂贵。如果可能,通过创建 BitmapFactory.Options 并将 inSampleSize 设置为 >1 来缩小负载。

                  编辑:另外,请务必检查您的应用程序是否存在内存泄漏。泄漏位图(使用static 位图是一种很好的方法)会很快耗尽您的可用内存。

                  【讨论】:

                  • 我不是在调试这个应用程序,只是在运行它。其余代码是 100% 相同的,那么从一个地方加载的位图怎么会泄漏而其他地方却没有呢?关于缩放 - 我想使用高质量的图像(应该有足够的内存来加载它们,因为它们从资产加载时不会出错)。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-06-27
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-08-10
                  相关资源
                  最近更新 更多