将图像加载到位图对象时出现奇怪的OutOfMemory问题

我有一个ListView,每行上有几个图像按钮。当用户单击列表行时,它会启动一个新活动。由于相机布局的问题,我不得不构建自己的标签。结果启动的活动是一个地图。如果我单击我的按钮启动图像预览(从SD卡加载图像),应用程序将从活动返回到ListView活动到结果处理程序,以重新启动我的新活动,它只不过是一个图像小部件。

ListView上的图像预览是使用光标和ListAdapter完成的。这使得它非常简单,但我不确定如何将调整大小的图像(即,更小的位大小而不是像素作为动态图像按钮的src。所以我只是调整了手机相机上的图像大小。

问题是,当它试图返回并重新启动第二个活动时,我得到了OutOfMemoryError

  • 有没有一种方法可以轻松地逐行构建列表适配器,在那里我可以动态调整大小(按位)?

这将是最好的,因为我还需要对每一行中的小部件/元素的属性进行一些更改,因为由于焦点问题,我无法使用触摸屏选择一行。(我可以用滚球。

  • 我知道我可以进行带外调整并保存我的图像,但这并不是我真正想做的,但一些示例代码会很好。

当我在ListView上禁用图像时,它再次正常工作。

FYI:我是这样做的:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,R.id.gpslat, R.id.imagefilename };notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);setListAdapter(notes);

其中R.id.imagefilenameButtonImage

这是我的LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed

我在显示图像时也有一个新的错误:

22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
645208 次浏览

我做了以下操作来拍摄图像并调整其大小。希望这有帮助

Bitmap bm;bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);mPicture = new ImageView(context);mPicture.setImageBitmap(bm);

要修复OutOfMemory错误,您应该执行以下操作:

BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 8;Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

inSampleSize选项可减少内存消耗。

这是一个完整的方法。首先它读取图像大小,而不解码内容本身。然后它找到最好的inSampleSize值,应该是2的幂,最后图像被解码。

// Decodes image and scales it to reduce memory consumptionprivate Bitmap decodeFile(File f) {try {// Decode image sizeBitmapFactory.Options o = new BitmapFactory.Options();o.inJustDecodeBounds = true;BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale tofinal int REQUIRED_SIZE=70;
// Find the correct scale value. It should be the power of 2.int scale = 1;while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&o.outHeight / scale / 2 >= REQUIRED_SIZE) {scale *= 2;}
// Decode with inSampleSizeBitmapFactory.Options o2 = new BitmapFactory.Options();o2.inSampleSize = scale;return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);} catch (FileNotFoundException e) {}return null;}

我对Fedor的代码做了一个小改进。它基本上做了同样的事情,但是没有了(在我看来)丑陋的这时循环,它总是导致2的幂。感谢Fedor做出了最初的解决方案,我一直卡住,直到我找到了他的解决方案,然后我才能够做出这个:)

 private Bitmap decodeFile(File f){Bitmap b = null;
//Decode image sizeBitmapFactory.Options o = new BitmapFactory.Options();o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);BitmapFactory.decodeStream(fis, null, o);fis.close();
int scale = 1;if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));}
//Decode with inSampleSizeBitmapFactory.Options o2 = new BitmapFactory.Options();o2.inSampleSize = scale;fis = new FileInputStream(f);b = BitmapFactory.decodeStream(fis, null, o2);fis.close();
return b;}

使用这个bitmap.recycle();这有助于没有任何画质问题。

我有一个更有效的解决方案,它不需要任何形式的缩放。只需解码您的位图一次,然后将其缓存在映射中,然后根据名称检索位图并将其设置在ImageView中。无需再做任何事情。

这将起作用,因为解码位图的实际二进制数据不存储在dalvik VM堆中。它存储在外部。因此,每次解码位图时,它都会在VM堆之外分配内存,而这些内存永远不会被GC回收

为了帮助你更好地理解这一点,假设你将你的图像保存在可绘制文件夹中。你只需通过执行getResources(). getDrwable(R.drawable.)来获取图像。这不会每次解码你的图像,而是在你每次调用它时重新使用已经解码的实例。所以本质上它是缓存的。

现在,由于您的图像位于某个文件中(甚至可能来自外部服务器),因此您有责任缓存解码的位图实例,以便在需要的任何地方重复使用。

希望这有帮助。

这里有两个问题……

  • 位图内存不在VM堆中,而是在本机堆中-请参阅BitmapFactory OOM快把我逼疯了
  • 本机堆的垃圾收集比VM堆更懒——所以每次你通过活动的onP暂停或onDestroy时,你都需要非常积极地执行bitmap.recycle和bitmap=null

这似乎是一个运行时间很长的问题,有很多不同的解释。我接受了这里最常见的两个答案的建议,但这两个答案都没有解决我的问题,即虚拟机声称它无法负担执行进程解码部分的字节。经过一些挖掘,我了解到这里真正的问题是解码过程从土著堆中拿走。

看这里:BitmapFactory OOM快把我逼疯了

这让我进入了另一个讨论话题,在那里我找到了解决这个问题的更多方法。一种是在显示图像后手动调用System.gc();。但这实际上会让你的应用使用更多的内存,以减少本机堆。2.0版本(Donut)更好的解决方案是使用BitmapFactory选项“inPur的”。所以我简单地在o2.inSampleSize=scale;之后添加了o2.inPurgeable=true;

更多关于这个话题:内存堆的限制只有6M吗?

现在,说了所有这些,我也是Java和Android的完全傻瓜。所以如果你认为这是解决这个问题的糟糕方法,你可能是对的。;-)但这对我来说很有效,我发现现在不可能从堆缓存中运行虚拟机。我能发现的唯一缺点是你正在破坏缓存的绘制图像。这意味着如果你直接回到该图像,每次都要重绘它。就我的应用程序的工作方式而言,这不是真正的问题。你的里程可能会有所不同。

这是一个已知bug,这不是因为大文件。由于Android缓存绘图,它会在使用少量图像后内存溢出。但我找到了另一种方法,通过跳过Android默认缓存系统。

解决方案:将图像移动到“资产”文件夹并使用以下函数获取BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {AssetManager assets = context.getResources().getAssets();InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));Bitmap bitmap = BitmapFactory.decodeStream(buffer);return new BitmapDrawable(context.getResources(), bitmap);}

我用以下方式解决了同样的问题。

Bitmap b = null;Drawable d;ImageView i = new ImageView(mContext);try {b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);b.eraseColor(0xFFFFFFFF);Rect r = new Rect(0, 0,320 , 424);Canvas c = new Canvas(b);Paint p = new Paint();p.setColor(0xFFC0C0C0);c.drawRect(r, p);d = mContext.getResources().getDrawable(mImageIds[position]);d.setBounds(r);d.draw(c);
/*BitmapFactory.Options o2 = new BitmapFactory.Options();o2.inTempStorage = new byte[128*1024];b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);o2.inSampleSize=16;o2.inPurgeable = true;*/} catch (Exception e) {
}i.setImageBitmap(b);

我遇到了同样的问题,并通过避免BitmapFactory.decodeStream或decdeFile函数来解决它,而是使用BitmapFactory.decodeFileDescriptor

decodeFileDescriptor看起来像它调用不同的本机方法,而不是解码流/解码文件。

不管怎样,有效的是这个(注意,我添加了一些选项,就像上面的一些一样,但这并不是区别。关键是调用BitmapFactory.decode文件描述符而不是解码流解码文件):

private void showImage(String path)   {
Log.i("showImage","loading:"+path);BitmapFactory.Options bfOptions=new BitmapFactory.Options();bfOptions.inDither=false;                     //Disable Dithering modebfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be clearedbfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the futurebfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);FileInputStream fs=null;try {fs = new FileInputStream(file);} catch (FileNotFoundException e) {//TODO do something intelligente.printStackTrace();}
try {if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);} catch (IOException e) {//TODO do something intelligente.printStackTrace();} finally{if(fs!=null) {try {fs.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);//bm.recycle();bm=null;}

我认为在dedeStream/dedeFile中使用的本机函数有问题。我已经确认在使用dedeFileDetptor时调用了不同的本机方法。我还读到的是图像(位图)不是以标准的Java方式分配的,而是通过本机调用分配的;分配是在虚拟堆之外完成的,但是算上它!"

上面的答案都不适合我,但我确实想出了一个非常丑陋的解决方法来解决这个问题。我在我的项目中添加了一个非常小的1x1像素图像作为资源,并将其加载到我的ImageView中,然后调用垃圾回收机制。我想可能是ImageView没有释放位图,所以GC一直没有把它捡起来。它很丑,但它现在似乎起作用了。

if (bitmap != null){bitmap.recycle();bitmap = null;}if (imageView != null){imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.}System.gc();
imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.

我来自iOS经验,我很沮丧地发现了一个问题,这个问题与加载和显示图像如此基本。毕竟,每个有这个问题的人都试图显示合理大小的图像。不管怎样,这里有两个改变,解决了我的问题(并使我的应用程序非常响应)。

1)每次做BitmapFactory.decodeXYZ()时,确保传入BitmapFactory.OptionsinPurgeable设置为true(最好将inInputShareable也设置为true)。

2)永远不要使用Bitmap.createBitmap(width, height, Config.ARGB_8888)。我的意思是永远不要!我从来没有遇到过几次后不引发内存错误的事情。无论recycle()System.gc()有什么帮助。它总是引发异常。另一种实际有效的方法是在你的绘图中有一个虚拟图像(或者你使用上述步骤1解码的另一个位图),重新缩放为任何你想要的,然后操纵结果位图(例如将其传递到画布上以获得更多乐趣)。所以,你应该使用的是:Bitmap.createScaledBitmap(srcBitmap, width, height, false)。如果出于任何原因你必须使用蛮力创建方法,那么至少通过Config.ARGB_4444

这几乎可以保证为您节省数小时甚至数天。所有关于缩放图像等的讨论都不起作用(除非您考虑错误的尺寸或降级的图像作为解决方案)。

我只是在几分钟前遇到了这个问题。我通过更好地管理我的listview适配器来解决它。我以为这是我使用的数百个50x50px图像的问题,事实证明,我试图在每次显示行时膨胀我的自定义视图。只需通过测试来查看行是否已膨胀,我就消除了这个错误,并且我使用了数百个位图。这实际上是针对微调器的,但基本适配器对ListView的工作方式相同。这个简单的修复也大大提高了适配器的性能。

@Overridepublic View getView(final int position, View convertView, final ViewGroup parent) {
if(convertView == null){LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);convertView = inflater.inflate(R.layout.spinner_row, null);}...

我花了一整天的时间测试这些解决方案,唯一对我有效的是上述获取图像并手动调用GC的方法,我知道这不应该是必要的,但当我将我的应用程序置于重载测试下时,这是唯一有效的方法在活动之间切换。我的应用程序在列表视图中有一个缩略图列表(假设活动A),当您单击其中一个图像时,它会将您带到另一个活动(假设活动B),该活动显示该项目的主图像。当我在两个活动之间来回切换时,我最终会收到OOM错误,应用程序会强制关闭。

当我在列表视图中走到一半时,它会崩溃。

现在,当我在活动B中实现以下内容时,我可以毫无问题地浏览整个列表视图,并继续前进,前进,前进……而且速度很快。

@Overridepublic void onDestroy(){Cleanup();super.onDestroy();}
private void Cleanup(){bitmap.recycle();System.gc();Runtime.getRuntime().gc();}

安卓培训教程类“高效显示位图”提供了一些很好的信息,用于理解和处理异常java.lang.OutOfMemoryError:加载位图时位图大小超出VM预算。


读取位图尺寸和类型

BitmapFactory类提供了几种解码方法(decodeByteArray()decodeFile()decodeResource()等),用于从各种来源创建一个Bitmap。根据您的图像数据源选择最合适的解码方法。这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory异常。每种类型的解码方法都有额外的签名,可以让您通过BitmapFactory.Options类指定解码选项。在解码时将inJustDecodeBounds属性设置为true可以避免内存分配,为位图对象返回null,但设置decodeByteArray()0、decodeByteArray()1和decodeByteArray()2。这种技术允许您在位图的构造(和内存分配)之前读取图像数据的尺寸和类型。

BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(getResources(), R.id.myimage, options);int imageHeight = options.outHeight;int imageWidth = options.outWidth;String imageType = options.outMimeType;

为了避免java.lang.OutOfMemory异常,请在解码位图之前检查位图的尺寸,除非您绝对信任源为您提供可预测大小的图像数据,这些数据可以舒适地适合可用内存。


将缩小的版本加载到内存中

现在知道了图像尺寸,它们可以用来决定是应该将完整图像加载到内存中,还是应该加载次采样版本。以下是一些需要考虑的因素:

  • 在内存中加载完整映像的估计内存使用量。
  • 给定应用程序的任何其他内存需求,您愿意承诺加载此映像的内存量。
  • 要加载图像的目标ImageView或UI组件的维度。
  • 屏幕尺寸和电流密度的装置。

例如,如果1024x768像素的图像最终以ImageView的128x96像素缩略图显示,则不值得将其加载到内存中。

要告诉解码器对图像进行子采样,将较小的版本加载到内存中,请在BitmapFactory.Options对象中将inSampleSize设置为true。例如,分辨率为2048x1536的图像以inSampleSize的4解码会产生大约512x384的位图。将其加载到内存中对完整图像使用0.75MB而不是12MB(假设位图配置为ARGB_8888)。这是一种根据目标宽度和高度计算样本大小值的方法:

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {// Raw height and width of imagefinal int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both// height and width larger than the requested height and width.while ((halfHeight / inSampleSize) > reqHeight&& (halfWidth / inSampleSize) > reqWidth) {inSampleSize *= 2;}}
return inSampleSize;}

说明:计算两个值的幂,因为解码器使用最终值,向下舍入到最接近的2的幂,根据inSampleSize留档

要使用此方法,首先将inJustDecodeBounds设置为true, pass the options through and then decode again using the new inSampleSizevalue andinJustDecodeB的set tofalse'解码:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensionsfinal BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSizeoptions.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize setoptions.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, resId, options);}

此方法可以轻松地将任意大尺寸的位图加载到显示100x100像素缩略图的ImageView中,如以下示例代码所示:

mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

您可以按照类似的过程从其他来源解码位图,根据需要替换适当的BitmapFactory.decode*方法。

这对我工作!

public Bitmap readAssetsBitmap(String filename) throws IOException {try {BitmapFactory.Options options = new BitmapFactory.Options();options.inPurgeable = true;Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);if(bitmap == null) {throw new IOException("File cannot be opened: It's value is null");} else {return bitmap;}} catch (IOException e) {throw new IOException("File cannot be opened: " + e.getMessage());}}

这样的OutofMemoryException不能通过调用System.gc()等来完全解决。

通过参考活动生命周期

活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级。

您可以考虑使用的每个位图图片的大小和分辨率。我建议减小大小,重新取样以降低分辨率,参考画廊的设计(一张小图片PNG,一张原始图片)。

此问题仅发生在Android模拟器中。我在模拟器中也遇到过这个问题,但当我签入设备时,它工作正常。

所以请签入一个设备。它可能是在设备中运行的。

最近我看到了很多关于OOM异常和缓存的问题。开发人员指南对此有一篇非常好的文章,但有些人往往无法以合适的方式实现它。

因此,我编写了一个示例应用程序来演示在Android环境中的缓存。此实现尚未获得OOM。

查看此答案的末尾以获取源代码的链接。

职位要求:

  • Android API 2.1或更高版本(我根本无法获得API 1.6中应用程序的可用内存-这是API 1.6中唯一不起作用的代码)
  • Android支持包

截图

产品特点:

  • 如果有方向更改,则保留缓存,使用单例
  • 将分配的应用程序内存的八分之一用于缓存(如果需要,请修改)
  • 大位图得到扩展(您可以定义要允许的最大像素)
  • 在下载位图之前控制有一个可用的互联网连接
  • 确保每行只实例化一项任务
  • 如果你在扔离开ListView,它根本不会下载之间的位图

这不包括:

  • 磁盘缓存。无论如何,这应该很容易实现-只需指向从磁盘获取位图的不同任务

示例代码:

正在下载的图像是来自Flickr的图像(75x75)。但是,请放置您想要处理的任何图像URL,如果超过最大值,应用程序将缩小它。在这个应用程序中,URL只是在String数组中。

#0有处理位图的好方法。然而,在这个应用程序中,我将LruCache的实例放在我创建的另一个缓存类中,以使应用程序更可行。

Cache.java的关键内容(loadBitmap()方法是最重要的):

public Cache(int size, int maxWidth, int maxHeight) {// Into the constructor you add the maximum pixels// that you want to allow in order to not scale images.mMaxWidth = maxWidth;mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {protected int sizeOf(String key, Bitmap b) {// Assuming that one pixel contains four bytes.return b.getHeight() * b.getWidth() * 4;}};
mCurrentTasks = new ArrayList<String>();}
/*** Gets a bitmap from cache.* If it is not in cache, this method will:** 1: check if the bitmap url is currently being processed in the* BitmapLoaderTask and cancel if it is already in a task (a control to see* if it's inside the currentTasks list).** 2: check if an internet connection is available and continue if so.** 3: download the bitmap, scale the bitmap if necessary and put it into* the memory cache.** 4: Remove the bitmap url from the currentTasks list.** 5: Notify the ListAdapter.** @param mainActivity - Reference to activity object, in order to* call notifyDataSetChanged() on the ListAdapter.* @param imageKey - The bitmap url (will be the key).* @param imageView - The ImageView that should get an* available bitmap or a placeholder image.* @param isScrolling - If set to true, we skip executing more tasks since* the user probably has flinged away the view.*/public void loadBitmap(MainActivity mainActivity,String imageKey, ImageView imageView,boolean isScrolling) {final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {imageView.setImageBitmap(bitmap);} else {imageView.setImageResource(R.drawable.ic_launcher);if (!isScrolling && !mCurrentTasks.contains(imageKey) &&mainActivity.internetIsAvailable()) {BitmapLoaderTask task = new BitmapLoaderTask(imageKey,mainActivity.getAdapter());task.execute();}}}

您不需要编辑Cache.java文件中的任何内容,除非您想实现磁盘缓存。

MainActivity.java的关键内容:

public void onScrollStateChanged(AbsListView view, int scrollState) {if (view.getId() == android.R.id.list) {// Set scrolling to true only if the user has flinged the// ListView away, hence we skip downloading a series// of unnecessary bitmaps that the user probably// just want to skip anyways. If we scroll slowly it// will still download bitmaps - that means// that the application won't wait for the user// to lift its finger off the screen in order to// download.if (scrollState == SCROLL_STATE_FLING) {mIsScrolling = true;} else {mIsScrolling = false;mListAdapter.notifyDataSetChanged();}}}
// Inside ListAdapter...@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {View row = convertView;final ViewHolder holder;
if (row == null) {LayoutInflater inflater = getLayoutInflater();row = inflater.inflate(R.layout.main_listview_row, parent, false);holder = new ViewHolder(row);row.setTag(holder);} else {holder = (ViewHolder) row.getTag();}
final Row rowObject = getItem(position);
// Look at the loadBitmap() method description...holder.mTextView.setText(rowObject.mText);mCache.loadBitmap(MainActivity.this,rowObject.mBitmapUrl, holder.mImageView,mIsScrolling);
return row;}

getView()经常被调用。如果我们没有实现检查以确保我们不会启动每行无限多的线程,那么在那里下载图像通常不是一个好主意。Cache.java检查rowObject.mBitmapUrl是否已经在任务中,如果是,它不会启动另一个任务。因此,我们很可能不会超过AsyncTask池的工作队列限制。

下载:

您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码。


最后一句话:

我已经测试了几个星期了,我还没有得到一个OOM异常。我已经在模拟器、我的Nexus One和我的Nexus S上测试了这个。我已经测试了包含质量HD图像的图像URL。唯一的瓶颈是下载需要更多的时间。

我可以想象OOM会出现的只有一种可能的情况,那就是如果我们下载了许多非常大的图像,在它们缩放并放入缓存之前,会同时占用更多的内存并导致OOM。但这甚至不是理想的情况,很可能无法以更可行的方式解决。

在评论中报告错误!:-)

这对我有用。

Bitmap myBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();options.InPurgeable = true;options.OutHeight = 50;options.OutWidth = 50;options.InSampleSize = 4;
File imgFile = new File(filepath);myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

这是在C#单机器人上。你可以很容易地改变图像的路径。这里重要的是要设置的选项。

这段代码将有助于从可绘制的文件中加载大位图

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {
Context context;
public BitmapUtilsTask(Context context) {this.context = context;}
/*** Loads a bitmap from the specified url.** @param url The location of the bitmap asset* @return The bitmap, or null if it could not be loaded* @throws IOException* @throws MalformedURLException*/public Bitmap getBitmap() throws MalformedURLException, IOException {
// Get the source image's dimensionsint desiredWidth = 1000;BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
int srcWidth = options.outWidth;int srcHeight = options.outHeight;
// Only scale if the source is big enough. This code is just trying// to fit a image into a certain width.if (desiredWidth > srcWidth)desiredWidth = srcWidth;
// Calculate the correct inSampleSize/scale value. This helps reduce// memory use. It should be a power of 2int inSampleSize = 1;while (srcWidth / 2 > desiredWidth) {srcWidth /= 2;srcHeight /= 2;inSampleSize *= 2;}// Decode with inSampleSizeoptions.inJustDecodeBounds = false;options.inDither = false;options.inSampleSize = inSampleSize;options.inScaled = false;options.inPreferredConfig = Bitmap.Config.ARGB_8888;options.inPurgeable = true;Bitmap sampledSrcBitmap;
sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
return sampledSrcBitmap;}
/*** The system calls this to perform work in a worker thread and delivers* it the parameters given to AsyncTask.execute()*/@Overrideprotected Bitmap doInBackground(Object... item) {try {return getBitmap();} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}}

这似乎是与社区分享我的加载和处理图像的实用程序类的合适地方,欢迎您使用它并自由修改它。

package com.emil;
import java.io.IOException;import java.io.InputStream;
import android.graphics.Bitmap;import android.graphics.BitmapFactory;
/*** A class to load and process images of various sizes from input streams and file paths.** @author Emil http://stackoverflow.com/users/220710/emil**/public class ImageProcessing {
public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);Bitmap bm = BitmapFactory.decodeStream(stream,null,options);if(ImageProcessing.checkDecode(options)){return bm;}else{throw new IOException("Image decoding failed, using stream.");}}
public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);Bitmap bm = BitmapFactory.decodeFile(imgPath,options);if(ImageProcessing.checkDecode(options)){return bm;}else{throw new IOException("Image decoding failed, using file path.");}}
public static Dimensions getDimensions(InputStream stream) throws IOException{BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();BitmapFactory.decodeStream(stream,null,options);if(ImageProcessing.checkDecode(options)){return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);}else{throw new IOException("Image decoding failed, using stream.");}}
public static Dimensions getDimensions(String imgPath) throws IOException{BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();BitmapFactory.decodeFile(imgPath,options);if(ImageProcessing.checkDecode(options)){return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);}else{throw new IOException("Image decoding failed, using file path.");}}
private static boolean checkDecode(BitmapFactory.Options options){// Did decode work?if( options.outWidth<0 || options.outHeight<0 ){return false;}else{return true;}}
/*** Creates a Bitmap that is of the minimum dimensions necessary* @param bm* @param min* @return*/public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){int newWidth, newHeight;switch(min.type){case WIDTH:if(bm.getWidth()>min.minWidth){newWidth=min.minWidth;newHeight=ImageProcessing.getScaledHeight(newWidth, bm);}else{// No resizenewWidth=bm.getWidth();newHeight=bm.getHeight();}break;case HEIGHT:if(bm.getHeight()>min.minHeight){newHeight=min.minHeight;newWidth=ImageProcessing.getScaledWidth(newHeight, bm);}else{// No resizenewWidth=bm.getWidth();newHeight=bm.getHeight();}break;case BOTH: // minimize to the maximum dimensioncase MAX:if(bm.getHeight()>bm.getWidth()){// Height needs to minimizedmin.minDim=min.minDim!=null ? min.minDim : min.minHeight;if(bm.getHeight()>min.minDim){newHeight=min.minDim;newWidth=ImageProcessing.getScaledWidth(newHeight, bm);}else{// No resizenewWidth=bm.getWidth();newHeight=bm.getHeight();}}else{// Width needs to be minimizedmin.minDim=min.minDim!=null ? min.minDim : min.minWidth;if(bm.getWidth()>min.minDim){newWidth=min.minDim;newHeight=ImageProcessing.getScaledHeight(newWidth, bm);}else{// No resizenewWidth=bm.getWidth();newHeight=bm.getHeight();}}break;default:// No resizenewWidth=bm.getWidth();newHeight=bm.getHeight();}return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);}
public static int getScaledWidth(int height, Bitmap bm){return (int)(((double)bm.getWidth()/bm.getHeight())*height);}
public static int getScaledHeight(int width, Bitmap bm){return (int)(((double)bm.getHeight()/bm.getWidth())*width);}
/*** Get the proper sample size to meet minimization restraints* @param dim* @param min* @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2* @return*/public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){switch(min.type){case WIDTH:return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);case HEIGHT:return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);case BOTH:int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);// Return the smaller of the twoif(widthMaxSampleSize<heightMaxSampleSize){return widthMaxSampleSize;}else{return heightMaxSampleSize;}case MAX:// Find the larger dimension and go bases on thatif(dim.width>dim.height){return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);}else{return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);}}return 1;}
public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){int add=multipleOf2 ? 2 : 1;int size=0;while(min<(dim/(size+add))){size+=add;}size = size==0 ? 1 : size;return size;}
public static class Dimensions {int width;int height;
public Dimensions(int width, int height) {super();this.width = width;this.height = height;}
@Overridepublic String toString() {return width+" x "+height;}}
public static class Minimize {public enum Type {WIDTH,HEIGHT,BOTH,MAX}Integer minWidth;Integer minHeight;Integer minDim;Type type;
public Minimize(int min, Type type) {super();this.type = type;switch(type){case WIDTH:this.minWidth=min;break;case HEIGHT:this.minHeight=min;break;case BOTH:this.minWidth=min;this.minHeight=min;break;case MAX:this.minDim=min;break;}}
public Minimize(int minWidth, int minHeight) {super();this.type=Type.BOTH;this.minWidth = minWidth;this.minHeight = minHeight;}
}
/*** Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config* @param width* @param height* @param config* @return*/public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){long pixels=width*height;switch(config){case ALPHA_8: // 1 byte per pixelreturn pixels;case ARGB_4444: // 2 bytes per pixel, but depreciatedreturn pixels*2;case ARGB_8888: // 4 bytes per pixelreturn pixels*4;case RGB_565: // 2 bytes per pixelreturn pixels*2;default:return pixels;}}
private static BitmapFactory.Options getOptionsForDimensions(){BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds=true;return options;}
private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = false;options.inDither = false;options.inSampleSize = sampleSize;options.inScaled = false;options.inPreferredConfig = bitmapConfig;return options;}}

一般android设备堆大小只有16MB(从设备/操作系统不同,见后堆大小),如果你正在加载的图像,它跨越16MB的大小,它会抛出内存溢出异常,而不是使用位图,从SD卡或资源加载图像,甚至从网络尝试使用获取图片uri,加载位图需要更多的内存,或者你可以设置位图为空,如果你的工作完成了位图。

我认为避免OutOfMemoryError的最好方法是面对它并理解它。

我做了一个应用程序来故意导致OutOfMemoryError,并监控内存使用情况。

在我用这个应用程序做了很多实验之后,我得到了以下结论:

我将首先讨论在蜂蜜梳子之前的SDK版本。

  1. 位图存储在本机堆中,但它会自动收集垃圾,调用回收()是不必要的。

  2. 如果{VM堆大小}+{分配的本机堆内存}>={设备的VM堆大小限制},并且您正在尝试创建位图,OOM将被抛出。

    注意:计算VM HEAP SIZE而不是VM分配内存。

  3. VM堆大小在增长后永远不会缩小,即使分配的VM内存缩小了。

  4. 因此,您必须将峰值VM内存保持在尽可能低的水平,以防止VM堆大小增长过大而无法为位图节省可用内存。

  5. 手动调用System.gc()是没有意义的,系统会在尝试增加堆大小之前先调用它。

  6. 本机堆大小也永远不会缩小,但它不会计入OOM,因此无需担心。

然后,让我们谈谈从蜂蜜梳子开始的SDK。

  1. 位图存储在VM堆中,本机内存不计入OOM。

  2. OOM的条件要简单得多:{VM堆大小}>={设备的VM堆大小限制}。

  3. 因此,您有更多可用内存来创建具有相同堆大小限制的位图,OOM不太可能被抛出。

以下是我对垃圾收集和内存泄漏的一些观察。

你可以在应用程序中自己看到它。如果一个活动执行了一个在活动被销毁后仍在运行的Async的任务,活动将不会得到垃圾回收,直到Async的任务完成。

这是因为AsyncTeam是一个匿名内部类的实例,它保存了活动的引用。

如果任务在后台线程的IO操作中被阻塞,则调用AsyncTask.cancel(true)不会停止执行。

回调也是匿名的内部类,因此如果项目中的静态实例持有它们而不释放它们,内存就会泄漏。

如果您计划了重复或延迟的任务,例如计时器,并且您没有在onP的()中调用取消()和清除(),内存将被泄漏。

我尝试了Thomas Vervest的方法,但当图像大小为2592x1944时,IMAGE_MAX_SIZE返回1。

这个版本对我来说是有效的,基于其他人提供的所有其他评论:

private Bitmap decodeFile (File f) {Bitmap b = null;try {// Decode image sizeBitmapFactory.Options o = new BitmapFactory.Options ();o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream (f);try {BitmapFactory.decodeStream (fis, null, o);} finally {fis.close ();}
int scale = 1;for (int size = Math.max (o.outHeight, o.outWidth);(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// Decode with inSampleSizeBitmapFactory.Options o2 = new BitmapFactory.Options ();o2.inSampleSize = scale;fis = new FileInputStream (f);try {b = BitmapFactory.decodeStream (fis, null, o2);} finally {fis.close ();}} catch (IOException e) {}return b;}

这里有很好的答案,但我想要一个完全可用类来解决这个问题……所以我做了一个。

这是我的BitmapHelper类,是OutOfMemoryError证明:-)

import java.io.File;import java.io.FileInputStream;
import android.graphics.Bitmap;import android.graphics.Bitmap.Config;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Matrix;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;
public class BitmapHelper{
//decodes image and scales it to reduce memory consumptionpublic static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty){try{//Decode image sizeBitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();bitmapSizeOptions.inJustDecodeBounds = true;BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
// load image using inSampleSize adapted to required image sizeBitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);bitmapDecodeOptions.inPurgeable = true;bitmapDecodeOptions.inDither = !quickAndDirty;bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
// scale bitmap to mathc required size (and keep aspect ratio)
float srcWidth = (float) bitmapDecodeOptions.outWidth;float srcHeight = (float) bitmapDecodeOptions.outHeight;
float dstWidth = (float) requiredWidth;float dstHeight = (float) requiredHeight;
float srcAspectRatio = srcWidth / srcHeight;float dstAspectRatio = dstWidth / dstHeight;
// recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'// (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid// java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8// I do not excatly understand why, but this way it's OK
boolean recycleDecodedBitmap = false;
Bitmap scaledBitmap = decodedBitmap;if (srcAspectRatio < dstAspectRatio){scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));// will recycle recycleDecodedBitmaprecycleDecodedBitmap = true;}else if (srcAspectRatio > dstAspectRatio){scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);recycleDecodedBitmap = true;}
// crop image to match required image size
int scaledBitmapWidth = scaledBitmap.getWidth();int scaledBitmapHeight = scaledBitmap.getHeight();
Bitmap croppedBitmap = scaledBitmap;
if (scaledBitmapWidth > requiredWidth){int xOffset = (scaledBitmapWidth - requiredWidth) / 2;croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);scaledBitmap.recycle();}else if (scaledBitmapHeight > requiredHeight){int yOffset = (scaledBitmapHeight - requiredHeight) / 2;croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);scaledBitmap.recycle();}
if (recycleDecodedBitmap){decodedBitmap.recycle();}decodedBitmap = null;
scaledBitmap = null;return croppedBitmap;}catch (Exception ex){ex.printStackTrace();}return null;}
/*** compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)** @param requiredWidth* @param requiredHeight* @param powerOf2*            weither we want a power of 2 sclae or not* @return*/public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2){int inSampleSize = 1;
// Raw height and width of imagefinal int srcHeight = options.outHeight;final int srcWidth = options.outWidth;
if (powerOf2){//Find the correct scale value. It should be the power of 2.
int tmpWidth = srcWidth, tmpHeight = srcHeight;while (true){if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)break;tmpWidth /= 2;tmpHeight /= 2;inSampleSize *= 2;}}else{// Calculate ratios of height and width to requested height and widthfinal int heightRatio = Math.round((float) srcHeight / (float) dstHeight);final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee// a final image with both dimensions larger than or equal to the// requested height and width.inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;}
return inSampleSize;}
public static Bitmap drawableToBitmap(Drawable drawable){if (drawable instanceof BitmapDrawable){return ((BitmapDrawable) drawable).getBitmap();}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());drawable.draw(canvas);
return bitmap;}
public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight){int width = bitmap.getWidth();int height = bitmap.getHeight();float scaleWidth = ((float) newWidth) / width;float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATIONMatrix matrix = new Matrix();// RESIZE THE BIT MAPmatrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAPBitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);return resizedBitmap;}
}

在我的一个应用程序中,我需要从Camera/Gallery拍照。如果用户单击相机中的图像(可能是2MP、5MP或8MP),图像大小从kBs到MBs不等。如果图像大小小于(或高达1-2MB)以上代码工作正常,但如果我的图像大小超过4MB或5MB,则OOM以帧形式出现:(

然后我努力解决这个问题,最后我对Fedor的代码进行了以下改进(所有的功劳都归功于Fedor做出了如此好的解决方案):)

private Bitmap decodeFile(String fPath) {// Decode image sizeBitmapFactory.Options opts = new BitmapFactory.Options();/** If set to true, the decoder will return null (no bitmap), but the* out... fields will still be set, allowing the caller to query the* bitmap without having to allocate the memory for its pixels.*/opts.inJustDecodeBounds = true;opts.inDither = false; // Disable Dithering modeopts.inPurgeable = true; // Tell to gc that whether it needs free// memory, the Bitmap can be clearedopts.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
BitmapFactory.decodeFile(fPath, opts);
// The new size we want to scale tofinal int REQUIRED_SIZE = 70;
// Find the correct scale value.int scale = 1;
if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {
// Calculate ratios of height and width to requested height and widthfinal int heightRatio = Math.round((float) opts.outHeight/ (float) REQUIRED_SIZE);final int widthRatio = Math.round((float) opts.outWidth/ (float) REQUIRED_SIZE);
// Choose the smallest ratio as inSampleSize value, this will guarantee// a final image with both dimensions larger than or equal to the// requested height and width.scale = heightRatio < widthRatio ? heightRatio : widthRatio;//}
// Decode bitmap with inSampleSize setopts.inJustDecodeBounds = false;
opts.inSampleSize = scale;
Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(Bitmap.Config.RGB_565, false);
return bm;
}

希望这能帮助到面临同样问题的朋友们!

更多请参考这个

将位图设置为Imageview后,像这样回收它:

bitmap.recycle();bitmap=null;

我的2美分:我用位图解决了我的OOM错误:

a)将我的图像缩放2倍

b)在我的自定义Adapter for a ListView中使用毕加索库,在getView中进行一次调用,如下所示:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

BitmapFactory.Options options = new Options();options.inSampleSize = 32;//img = BitmapFactory.decodeFile(imageids[position], options);
Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);theImage.recycle();theImage = null;System.gc();//ivlogdp.setImageBitmap(img);Runtime.getRuntime().gc();

使用这个概念这将帮助你,之后,设置图像位图上的图像视图

public static Bitmap convertBitmap(String path)   {
Bitmap bitmap=null;BitmapFactory.Options bfOptions=new BitmapFactory.Options();bfOptions.inDither=false;                     //Disable Dithering modebfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be clearedbfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the futurebfOptions.inTempStorage=new byte[32 * 1024];

File file=new File(path);FileInputStream fs=null;try {fs = new FileInputStream(file);} catch (FileNotFoundException e) {e.printStackTrace();}
try {if(fs!=null){bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);}} catch (IOException e) {
e.printStackTrace();} finally{if(fs!=null) {try {fs.close();} catch (IOException e) {
e.printStackTrace();}}}
return bitmap;}

如果你想让一个小图像从大图像的高度和宽度像60和60和滚动列表视图快速然后使用这个概念

public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,reqHeight);
// Decode bitmap with inSampleSize setoptions.inJustDecodeBounds = false;Bitmap bmp = BitmapFactory.decodeFile(path, options);return bmp;}
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
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;}

我希望它能帮助你很多。

您可以从开发者网站这里获得帮助

这里的所有解决方案都需要设置IMAGE_MAX_SIZE。这限制了具有更强大硬件的设备,如果图像大小太低,则在HD屏幕上看起来很难看。

我想出了一个解决方案,适用于我的三星Galaxy S3和其他几个设备,包括功能较弱的设备,使用功能更强大的设备时画质更好。

它的要点是计算在特定设备上为应用程序分配的最大内存,然后将比例设置为尽可能低,而不会超过此内存。这是代码:

public static Bitmap decodeFile(File f){Bitmap b = null;try{// Decode image sizeBitmapFactory.Options o = new BitmapFactory.Options();o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);try{BitmapFactory.decodeStream(fis, null, o);}finally{fis.close();}
// In Samsung Galaxy S3, typically max memory is 64mb// Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap// If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb// We try use 25% memory which equals to 16mb maximum for one bitmaplong maxMemory = Runtime.getRuntime().maxMemory();int maxMemoryForImage = (int) (maxMemory / 100 * 25);
// Refer to// http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html// A full screen GridView filled with images on a device with// 800x480 resolution would use around 1.5MB (800*480*4 bytes)// When bitmap option's inSampleSize doubled, pixel height and// weight both reduce in halfint scale = 1;while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)scale *= 2;
// Decode with inSampleSizeBitmapFactory.Options o2 = new BitmapFactory.Options();o2.inSampleSize = scale;fis = new FileInputStream(f);try{b = BitmapFactory.decodeStream(fis, null, o2);}finally{fis.close();}}catch (IOException e){}return b;}

我将此位图使用的最大内存设置为最大分配内存的25%,你可能需要根据需要进行调整,并确保此位图已清理,并且在使用完成后不会留在内存中。通常我使用此代码来执行图像旋转(源和目标位图),因此我的应用程序需要同时在内存中加载2个位图,并且25%在执行图像旋转时为我提供了一个良好的缓冲区,而不会运行内存溢出。

希望这能帮助到外面的人。

您好,请访问链接http://developer.android.com/training/displaying-bitmaps/index.html

或者只是尝试使用给定的函数检索位图

private Bitmap decodeBitmapFile (File f) {Bitmap bitmap = null;try {// Decode image sizeBitmapFactory.Options o = new BitmapFactory.Options ();o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream (f);try {BitmapFactory.decodeStream (fis, null, o);} finally {fis.close ();}
int scale = 1;for (int size = Math.max (o.outHeight, o.outWidth);(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// Decode with input-stram SampleSizeBitmapFactory.Options o2 = new BitmapFactory.Options ();o2.inSampleSize = scale;fis = new FileInputStream (f);try {bitmap  = BitmapFactory.decodeStream (fis, null, o2);} finally {fis.close ();}} catch (IOException e) {}return bitmap ;}

要修复OutOfMemory,您应该这样做,请尝试此代码

public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {Bitmap bitmap = null;InputStream in = null;options.inSampleSize=4;try {in = OpenHttpConnection(URL);Log.e("In====>", in+"");bitmap = BitmapFactory.decodeStream(in, null, options);Log.e("URL====>", bitmap+"");
in.close();} catch (IOException e1) {}return bitmap;}

try {BitmapFactory.Options bmOptions;bmOptions = new BitmapFactory.Options();bmOptions.inSampleSize = 1;if(studentImage != null){galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);}
galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());Log.e("Image_Url==>",IMAGE_URL+studentImage+"");
} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}

使用这些代码为每个图像选择从SdCard或drewable转换位图对象。

Resources res = getResources();WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);Display display = window.getDefaultDisplay();@SuppressWarnings("deprecation")int width = display.getWidth();@SuppressWarnings("deprecation")int height = display.getHeight();try {if (bitmap != null) {bitmap.recycle();bitmap = null;System.gc();}bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos).getPath()),width, height, true);} catch (OutOfMemoryError e) {if (bitmap != null) {bitmap.recycle();bitmap = null;System.gc();}BitmapFactory.Options options = new BitmapFactory.Options();options.inPreferredConfig = Config.RGB_565;options.inSampleSize = 1;options.inPurgeable = true;bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos).getPath().toString(), options), width, height,true);}return bitmap;

使用ImageData_Path.get(img_pos). getPath()的图像路径instend。

在较旧的设备(蜂巢或ICS或任何低端型号设备)中,尝试在应用程序标记下的清单文件中使用android:largeHeap="true",或通过使用以下代码减小位图的大小。

Bitmap bMap;BitmapFactory.Options options = new BitmapFactory.Options();options.InSampleSize = 8;bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

您也可以给4或12或16以减少位图大小

不幸的是如果以上都不起作用,则将其添加到您的清单文件中。在应用标签内

 <applicationandroid:largeHeap="true"

如果你像我一样懒惰,你可以开始使用毕加索图书馆加载图像。

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);Picasso.with(context).load(new File(...)).into(imageView3);

我使用了对我有用的解码文件描述符:

 FileInputStream  fileInputStream = null;try {fileInputStream  = new FileInputStream(file);FileDescriptor fd = fileInputStream.getFD();Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,816);imageView.setImageBitmap(imageBitmap);} catch (Exception e) {e.printStackTrace();}finally {if(fileInputStream != null){try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}

从文件描述符解码采样位图的代码:

 /*** Decode and sample down a bitmap from a file input stream to the requested width and height.** @param fileDescriptor The file descriptor to read from* @param reqWidth       The requested width of the resulting bitmap* @param reqHeight      The requested height of the resulting bitmap* @return A bitmap sampled down from the original with the same aspect ratio and dimensions* that are equal to or greater than the requested width and height*/public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensionsfinal BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSizeoptions.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize setoptions.inJustDecodeBounds = false;return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);}
/*** Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding* bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates* the closest inSampleSize that will result in the final decoded bitmap having a width and* height equal to or larger than the requested width and height. This implementation does not* ensure a power of 2 is returned for inSampleSize which can be faster when decoding but* results in a larger bitmap which isn't as useful for caching purposes.** @param options   An options object with out* params already populated (run through a decode**                  method with inJustDecodeBounds==true* @param reqWidth  The requested width of the resulting bitmap* @param reqHeight The requested height of the resulting bitmap* @return The value to be used for inSampleSize*/public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {// Raw height and width of imagefinal int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and widthfinal int heightRatio = Math.round((float) height / (float) reqHeight);final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee a final image// with both dimensions larger than or equal to the requested height and width.inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
// This offers some additional logic in case the image has a strange// aspect ratio. For example, a panorama may have a much larger// width than height. In these cases the total pixels might still// end up being too large to fit comfortably in memory, so we should// be more aggressive with sample down the image (=larger inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down furtherfinal float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {inSampleSize++;}}return inSampleSize;}

在浏览了所有答案后,我惊讶地发现没有人提到用于处理图像的Glide API。很棒的库,抽象出了位图管理的所有复杂性。您可以使用这个库和一行代码快速加载和调整图像大小。

     Glide.with(this).load(yourImageResource).into(imageview);

您可以在此处获取存储库:https://github.com/bumptech/glide

将以下行添加到manifest.xml文件中:

<application
android:hardwareAccelerated="false"android:largeHeap="true">
<activity></activity>
</application>

这将获得适当的位图并减少内存消耗

java

Bitmap bm = null;
BitmapFactory.Options bmpOption = new BitmapFactory.Options();bmpOption.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(file);BitmapFactory.decodeStream(fis, null, bmpOption);fis.close();
int scale = 1;
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) {scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /(double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5)));}
BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();bmpOption2.inSampleSize = scale;fis = new FileInputStream(file);bm = BitmapFactory.decodeStream(fis, null, bmpOption2);fis.close();

静态编程语言

val bm:Bitmap = nullval bmpOption = BitmapFactory.Options()bmpOption.inJustDecodeBounds = trueval fis = FileInputStream(file)BitmapFactory.decodeStream(fis, null, bmpOption)fis.close()val scale = 1if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE){scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()}val bmpOption2 = BitmapFactory.Options()bmpOption2.inSampleSize = scalefis = FileInputStream(file)bm = BitmapFactory.decodeStream(fis, null, bmpOption2)fis.close()

避免内存泄漏或位图OOM的最佳实践

  1. 不要将位图引用长期保留到Context/Activity。
  2. 如果您在应用程序中使用大位图作为背景或其他内容,请不要将完整图像拉到主存储器中。您可以使用位图的insamplesize属性来调整屏幕所需的大小。
  3. 清理位图引用一次不再使用。

我需要将大尺寸图像加载到位图中,我使用Glide来解决这个问题。首先使用BitmapFactory检查图像大小。我用c#编写,因为我使用Xamarin,所以需要一点调整才能在Java中使用。滑翔库留档

private Bitmap DecodeFile(File file) {// Decode image sizeBitmapFactory.Options options = new BitmapFactory.Options();        
// setting inJustDecodeBounds to true won't load the file into memory,// but gives you the actual file size.options.InJustDecodeBounds = true;BitmapFactory.decodeStream(new FileInputStream(file), null, options);int actualWidth = options.OutWidth;int actualHeight = options.OutHeight;                
var ratio = (double)actualHeight / actualWidth;
// Default to 800 x 600. changed the size whatever you need.var desiredWidth = 800;var desiredHeight = 600;
if(actualHeight > actualWidth){var ratio = (double)actualWidth / actualHeight;var futureTarget = Glide.With(Application.Context).AsBitmap().Load(file).SetDiskCacheStrategy(DiskCacheStrategy.None).SkipMemoryCache(true).Submit((int)(desiredWidth * ratio), desiredWidth);bitmap = futureTarget.Get() as Bitmap;}else{var ratio = (double)actualHeight / actualWidth;var futureTarget = Glide.With(Application.Context).AsBitmap().Load(file).SetDiskCacheStrategy(DiskCacheStrategy.None).SkipMemoryCache(true).Submit(desiredWidth, (int)(desiredWidth * ratio));bitmap = futureTarget.Get() as Bitmap;}return bitmap;}