2010-11-05 104 views
17

在我的应用程序中,我加载了来自JPEG和PNG文件的几个图像。当我把所有这些文件转化为资产目录,并以这种方式加载它,一切都很好:从外部存储器加载位图时发生OutOfMemory异常

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

但是,当我尝试从SD卡中加载完全相同的图像,我得到一个内存不足的异常!

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卡)。

+0

在什么条件下,你测试上面的代码?在仿真器,或真正的设备连接USB?有可能您的USB模式设置为锁定SD卡的磁盘模式。 – xandy 2010-11-05 00:59:36

+0

我在模拟器中运行这段代码。 – Fixpoint 2010-11-05 07:37:19

+0

jpg/png文件的大小是多少? – Fedor 2010-11-08 08:10:50

回答

4

你的API使用可能没有问题,我猜我们所能做的只是推断使用AssetManager涉及较少的幕后堆分配而不是从SD卡中打开一个随机文件。

800KB是任何人的书中的严重分配......这无疑将是解压缩的图像像素。鉴于你知道图像的大小,它有多深?如果它是32bpp,则尝试使用inPreferredConfig覆盖该值。

+0

所以,看起来似乎只有* emulator上的AssetManager *可以避免总加载的位图大小上限。 :( – Fixpoint 2010-11-13 22:05:51

5
  • 使用位图做很多事情时,请不要调试应用程序 - 只需运行它。调试器会留下内存泄漏。
  • 位图非常昂贵。如果可能,通过创建BitmapFactory.Options并将inSampleSize设置为> 1来缩小负载。

编辑:另外,一定要检查你的应用程序的内存泄漏。泄漏位图(具有static位图是一种很好的方法)很快就会耗尽可用内存。

+0

我没有调试这个应用程序,只是运行它。其余的代码是100%相同的,那么怎么可能从一个地方加载的位图发生泄漏,而另一些则没有?关于缩放 - 我想使用高质量的图像(应该有足够的内存来加载它们,因为它们的加载没有来自资产的错误)。 – Fixpoint 2010-11-05 07:40:21

1

为什么不直接使用getCacheDir()将图像移动到手机内部存储器的缓存中,或者使用临时目录来存储图像?

有关外部存储器使用情况,请参阅this,this。此外,this article可能与您有关。

8

我尝试了所有的办法,在其他资源提到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:所有指向可轻松访问的对象的SoftReferences在VM将抛出OutOfMemoryError之前保证被清除。

0

试试这个方法......

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

尝试过,与InputStream:OutOfMemory相同的结果 – Fixpoint 2010-11-09 22:42:03

0

您不能依赖GC回收您的位图内存。 不需要时,您必须清楚地回收位图。

见位图方法:

无效循环() 免费了这个位图的像素相关联的内存,并标记位图作为“死”的,这意味着它会抛出一个异常的getPixels()或的setPixels( )被调用,并且不会画任何东西。

+0

我不想回收所有这些位图,我正在使用它们 – Fixpoint 2010-11-10 14:13:58

+0

如果一个位图对用户隐藏,最好回收它们,如果它们再次显示,你再次加载它们。手机的内存是有限的。 – imcaptor 2010-11-11 05:05:05

+0

当试图加载已回收的位图时,它会抛出一个异常,说不能加载回收的位图... – 66CLSjY 2010-12-22 05:51:47

3

这是一个相当普遍的问题,我们所有人都在从SD卡加载图像时遇到的问题。

我找到的解决方案是在使用decodeFileDescriptor加载图像时首先使用inJustDecodeBounds。这实际上不会解码图像,但会给图像大小。现在我可以适当缩放(使用选项),以调整显示区域的图像大小。它的需要,因为手机上的低内存可以很容易地被你的5MP图像接管。我相信这是最优雅的解决方案。

+0

我正在考虑它,但这也意味着我会为一个图像提出2个请求 – Bostone 2011-10-30 16:42:15

+0

是的,但由于两次调用我可能因为inJustDecodeBounds没有做很多工作而面临任何减速 – the100rabh 2011-11-01 13:48:57

2

这里有两个问题....

  • 位图的内存是不是在VM堆而是在机堆 - 见BitmapFactory OOM driving me nuts
  • 垃圾收集本地堆是懒比VM堆 - 所以你需要相当积极地做bitmap.recycle和位图=空你每次通过一个活动的onPause或onDestroy
0

我发现开发Android应用程序最常见的错误之一是“ java.lang.OutOfMemoryErro r:位图大小超出VM预算“错误。在改变方向后,我发现这个错误使用了很多位图:活动被破坏,再次创建,布局从耗费虚拟机内存的XML中“膨胀”,可用于位图。

上一个活动布局的位图由于垃圾收集器已交叉引用其活动而未正确释放。经过多次实验,我发现了一个很好的解决方案。

首先,设置在您的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" 
    > 
    ... 

然后,在您的活动的的onDestroy()方法,调用unbindDrawables()方法传递refence到父视图,然后做一个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. 删除在每个孩子的ViewGroup中
0

使用下面的代码,你绝不会出现以下错误:java.lang中。的OutOfMemoryError:位图大小超过VM预算

   BitmapFactory.Options bounds = new BitmapFactory.Options(); 

       bounds.inSampleSize = 4; 

       myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds); 

       picturesView.setImageBitmap(myBitmap); 
4
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; 
    } 
1

感谢所有的线程,我发现对我的作品在真实设备上的解决方案。 的招数都是关于使用

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

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

您可以根据变化的getLength inSampleSize()来防止内存溢出这样的:

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; 
} 
相关问题