Android:使用2MB可用堆空间解码400KB文件的BitmapFactory.decodeStream()出现内存不足问题。

68

我的应用程序在源代码的以下行出现了OOM错误:

image = BitmapFactory.decodeStream(assetManager.open(imgFilename));

在导致应用程序因 OOM 错误而被杀死之前的分配:

(...)
08-05 21:22:12.443: I/dalvikvm-heap(2319): Clamp target GC heap from 25.056MB to 24.000MB
08-05 21:22:12.443: D/dalvikvm(2319): GC_FOR_MALLOC freed <1K, 50% free 2709K/5379K, external 18296K/19336K, paused 58ms
08-05 21:22:14.513: D/dalvikvm(2319): GC_EXTERNAL_ALLOC freed <1K, 50% free 2709K/5379K, external 18296K/19336K, paused 101ms
08-05 21:22:14.903: I/dalvikvm-heap(2319): Clamp target GC heap from 25.073MB to 24.000MB
08-05 21:22:14.903: D/dalvikvm(2319): GC_FOR_MALLOC freed 0K, 50% free 2709K/5379K, external 18312K/19336K, paused 53ms
08-05 21:22:22.843: D/ddm-heap(2319): Heap GC request
08-05 21:22:22.963: I/dalvikvm-heap(2319): Clamp target GC heap from 25.073MB to 24.000MB
08-05 21:22:22.963: D/dalvikvm(2319): threadid=1: still suspended after undo (sc=1 dc=1)
08-05 21:22:22.963: D/dalvikvm(2319): GC_EXPLICIT freed 1K, 50% free 2710K/5379K, external 18312K/19336K, paused 116ms

DDMS报告了关于堆状态的类似情况:

Heap Size:  5.254 MB
Allocated:  2.647 MB
Free:   2.607 MB
%Used:  50.38%
#Objects    49,028  

单步越过此行会导致OOM错误:
08-05 21:26:04.783: D/dalvikvm(2319): GC_EXTERNAL_ALLOC freed <1K, 50% free 2710K/5379K, external 18312K/19336K, paused 57ms
08-05 21:26:05.023: E/dalvikvm-heap(2319): 2097152-byte external allocation too large for this process.
08-05 21:26:05.163: I/dalvikvm-heap(2319): Clamp target GC heap from 25.073MB to 24.000MB
08-05 21:26:05.163: E/GraphicsJNI(2319): VM won't let us allocate 2097152 bytes
08-05 21:26:05.163: D/dalvikvm(2319): GC_FOR_MALLOC freed 0K, 50% free 2710K/5379K, external 18312K/19336K, paused 30ms
08-05 21:26:05.283: D/skia(2319): --- decoder->decode returned false
  1. "imgFileName"所引用的文件大小在Windows上报告为小于400K,那么为什么BitmapFactory.decodeStream要尝试分配2MB的内存空间?
  2. 为什么似乎有足够的可用空间,却会出现OOM错误?

此应用程序的目标是Android 2.2及以上版本。

8个回答

92

Android库在加载图片方面不太智能,因此您需要为此创建解决方法。

在我的测试中,Drawable.createFromStreamBitmapFactory.decodeStream使用更多内存。

您可以更改颜色方案以减少内存(RGB_565),但图像质量也会降低:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

参考资料:http://developer.android.com/reference/android/graphics/Bitmap.Config.html

您也可以载入一个经过缩放的图片,这将大大降低内存的使用率,但是您必须了解您的图片,以避免太多的质量损失。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

参考: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html

要动态定义inSampleSize,您可能需要知道图像大小以做出决策:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeStream(stream, null, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;

options.inJustDecodeBounds = false;
// recreate the stream
// make some calculation to define inSampleSize
options.inSampleSize = ?;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

您可以根据设备的屏幕大小自定义inSampleSize。要获取屏幕大小,您可以执行以下操作:

您可以根据设备的屏幕大小自定义inSampleSize。要获取屏幕大小,您可以执行以下操作:

DisplayMetrics metrics = new DisplayMetrics();
((Activity) activity).getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;

其他教程:


1
一个如何动态计算inSampleSize的图像大小计算公式是什么? - NinjaCoder
1
我已经编辑了问题,包括回答您的问题! - Paulo Cheque
1
屏幕尺寸并非图像尺寸。我有一个应用程序,允许用户拍摄或选择照片。拍摄照片可以使用设备的完整相机分辨率或更低的设置取决于设置。选择照片可以是任何大小,并且由于Picassa存储等原因,它不必是本地的。因此,我的应用程序会返回一个URI。无法知道URI下面是什么,也不知道大小。那么我该将采样大小设置为多少呢?我无法相信没有好的方法来实现这一点。 - Paul
更改为RGB_55并缩放图像对我有帮助。希望这个错误不会再出现。感谢您简化了Google文档! - Clocker

42

29
使用谷歌的建议后,我仍然遇到了OutOfMemoryError错误...很沮丧。 - toto_tata

8

磁盘上的文件大小不一定与内存中的文件大小相符。文件很可能是压缩过的,解压后会变得更大。您需要在计算时考虑到这一点。

将图像的尺寸(宽度x高度)乘以图像的颜色深度,即可得到图像在内存中的大小。


3
基本上,您可以通过尝试缩放Bitmap来解决问题,并且您将看到内存消耗减少。要执行此操作,您可以复制此处显示的方法。

此外,Android开发人员有一个专门的页面,可以帮助您更好地了解如何加载大型位图。请查看官方文档


1
虽然上述答案显然是正确的,但更好的做法也是在不再使用ImageView时将其bitmap/src属性明确设置为null,特别是在销毁活动时。 任何其他重型资源(大文本、音频、视频等)也可以被置为null。 这样可以确保资源立即被释放,而不是等待GC进行收集。

社区,你们能否对这种方法提供一些反馈意见! - Kyryl Zotov

0

您可以尝试使用Glide库来分配位图。Glide会使用最适合您的设备和环境的方法将位图文件传输到图形层(ImageView)。

简单易行:

Glide.with(getContext())
            .load(imageFile.getPath())
            .into(previewImageView);

-2

我使用以下方法解决了OutOfMemoryError问题

private Bitmap downloadImageBitmap(String sUrl) {
        this.url = sUrl;
        bitmap = null;
                    try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            for (options.inSampleSize = 1; options.inSampleSize <= 32; options.inSampleSize++) {
                InputStream inputStream = new URL(sUrl).openStream();  
                try {
                    bitmap = BitmapFactory.decodeStream(inputStream, null, options);      
                    inputStream.close();
                    break;
                } catch (OutOfMemoryError outOfMemoryError) {
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }

-3

我将样本大小增加了2。我的问题得到了解决。但请确保图像质量不会受到破坏。


2
仅仅改变inSampleSize而不知道你所做的,也不加解释,不应该是一个答案。 - JacksOnF1re

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接