Android 从外部存储器加载位图时发生OutOfMemory异常
在我的应用程序中,我从JPEG和PNG文件中加载了一些图像。当我将所有这些文件放入assets目录并以这种方式加载时,一切正常:Android 从外部存储器加载位图时发生OutOfMemory异常,android,out-of-memory,Android,Out Of Memory,在我的应用程序中,我从JPEG和PNG文件中加载了一些图像。当我将所有这些文件放入assets目录并以这种方式加载时,一切正常: InputStream stream = getAssets().open(path); Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); stream.close(); return new BitmapDrawable(bitmap); 但当我尝试从sd卡加载完全相同的图像时,我得到了
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卡)中。- 使用位图进行大量操作时,不要调试应用程序,只需运行它即可。调试器将留下内存泄漏
- 位图非常昂贵。如果可能,通过创建
并将BitmapFactory.Options
设置为>1,在加载时缩小它们的比例inSampleSize
编辑:另外,一定要检查你的应用程序是否存在内存泄漏。泄漏位图(使用
静态
位图是一种很好的方法)会很快耗尽可用内存。与其直接从SD卡加载位图,不如使用getCacheDir()将图像移动到手机内部存储器中的缓存中,或者使用临时目录将图像存储在内存中
请参阅,关于外部内存使用。此外,可能与您有关。您的API使用可能没有问题,我想我们所能做的就是推断使用AssetManager涉及的幕后堆分配比从SD卡打开随机文件少
在任何人的书中,800KB都是一个严肃的分配。。。这无疑是用于解压缩图像像素的。假设您知道图像的大小,那么图像的深度是多少?如果是32bpp,则尝试使用覆盖该值。我尝试了所有提到的方法&在其他资源中,但我得出结论,将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;
}
&你完了
注意:虽然它可以解决上述问题,但我建议您检查的优化图像加载
还要检查:在VM抛出OutOfMemoryError之前,所有指向软访问对象的软引用都保证被清除。尝试另一种方法
Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");
您不能依赖GC来回收位图内存。 不需要位图时,必须清楚地循环使用位图 请参见位图方法: 无效回收()
释放与此位图像素相关的内存,并将位图标记为“死”,这意味着如果调用getPixels()或setPixels(),它将引发异常,并且不会绘制任何内容。这是我们所有人在从SD卡加载图像时都会遇到的一个相当常见的问题
我发现的解决方案是在使用加载图像时首先使用。这实际上不会解码图像,但会给出图像大小。现在我可以适当地缩放它(使用选项),以便调整显示区域的图像大小。这是必要的,因为手机内存不足,很容易被5MP图像取代。我认为这是最优雅的解决方案。这里有两个问题
- 位图内存不在VM堆中,而是在本机堆中-请参阅
- 本机堆的垃圾收集比VM堆慢,因此每次执行活动的onPause或onDestroy时,都需要非常积极地执行bitmap.recycle和bitmap=null
<?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()方法,将引用传递给父视图,然后执行System.gc()操作
@覆盖
受保护的空onDestroy(){
super.ondestory();
不可绑定的drawables(findviewbyd(R.id.RootView));
gc();
}
私有void未绑定可提取项(视图){
if(view.getBackground()!=null){
view.getBackground().setCallback(null);
}
if(视图组的视图实例){
对于(int i=0;i<((视图组)视图)。getChildCount();i++){
未绑定的Drawables(((视图组)视图).getChildAt(i));
}
((视图组)视图);
}
}
此unbindDrawables()方法递归探索视图树,并:
使用下面的代码,您将永远不会得到以下错误:java.lang.OutOfMemoryError:位图大小超过VM预算
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inSampleSize = 4;
myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds);
picturesView.setImageBitmap(myBitmap);
多亏了所有的线程,我找到了一个在真正的设备上适合我的解决方案。 这些技巧都是关于使用
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;
//使用inSampleSize进行解码
BitmapFactory.Options o2=新的BitmapFactory.Options();
//o2.inSampleSize=刻度;
浮点数trueScale=o.outWidth/1024;
o2.InPurgable=真;
o2.inDither=假;
位图b=null;
做{
o2.inSampleSize=(int)trueScale;
Log.d(标记“刻度为”
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger
// 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;
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;
}