位图解码流内存溢出异常

5

我在我的应用程序中使用自己实现的Android ViewFlow示例。我从Web服务下载加密图像,然后将它们保存到SD卡上。我使用viewflow动态解密图像并显示它们。但是问题是,当用户开始快速更改图像时,它会抛出OutOfMemoryException异常,而我找到/测试的所有信息都无法解决我的情况。这是我正在使用的:

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.image_item, null);
    }

    try {
        File bufferFile = new File(ids.get(position));
        FileInputStream fis   = new FileInputStream(bufferFile);

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec("01234567890abcde".getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec("fedcba9876543210".getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        CipherInputStream cis = new CipherInputStream(fis, cipher);

        BitmapFactory.Options o = new BitmapFactory.Options();
        final int REQUIRED_SIZE=300*1024;

        //Find the correct scale value. It should be the power of 2.
        int width_tmp= o.outWidth, height_tmp= o.outHeight;
        int scale=1;
        while(true){
            if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                break;
            width_tmp/=2;
            height_tmp/=2;
            scale*=2;
        }

        //Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize=scale;

        Bitmap ops = BitmapFactory.decodeStream(cis,null,o2);
        ((ImageView) convertView.findViewById(R.id.imgView)).setImageBitmap(ops);
        cis.close();
        fis.close();

        System.gc();

    } catch (Exception e) {
        e.printStackTrace();
        ((ImageView) convertView.findViewById(R.id.imgView)).setImageResource(R.drawable.image_unavailablee);
    }

    return convertView;
}

我还是在这一行代码抛出了异常:

((ImageView) convertView.findViewById(R.id.imgView)).setImageBitmap(ops);

异常信息如下:

java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=6791KB, Allocated=3861KB, Bitmap Size=26006KB)

有什么想法来解决这个问题吗?
6个回答

7

这是我见过的最好、最干净、最重要的是最快的方法。非常感谢您的分享。有时候安卓组或博客创造的信息是最好的,但那是我寻找信息的最后一个地方。 - RayHaque

4

REQUIRED_SIZE 应包含最大尺寸(以像素为单位的宽度和高度),例如:

  final int REQUIRED_SIZE = 1024; // 1024 pixels wide or long.

你还错过了一些获取图像边界到BitmapFactory.Options o的代码行,然后再计算缩放因子。
    //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(cis, null, o);

    //The new size we want to scale to
    final int REQUIRED_SIZE = 1024;

然后使用o.outWidtho.outHeight来计算比例因子。您可能需要再次获取cis以实际解码流。

更新:

同时,您可以将以下变量作为适配器的成员并在构造函数中初始化。

SecretKeySpec keySpec = new SecretKeySpec("01234567890abcde".getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec("fedcba9876543210".getBytes());

这应该没有任何问题。


2

我一再遇到同样的问题。

这是我的代码,可能有点冗长,但由于摄像头大小、分辨率等不同,我必须这样做,但你需要根据自己的需求进行调整。

            BitmapFactory.Options imageOptions = new BitmapFactory.Options();
        imageOptions.inJustDecodeBounds = true;
        ByteArrayInputStream imageByteArrayInputStream = new ByteArrayInputStream(data);
        BitmapFactory.decodeStream(imageByteArrayInputStream, null, imageOptions);
        System.gc();

        // Decode frame size
        BitmapFactory.Options frameOptions = new BitmapFactory.Options();
        frameOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), selectedFrameResourceID, frameOptions);
        System.gc();

        // Scale factor for pre scaling
        int preScaleFactor = 1;
        if (imageOptions.outWidth > frameOptions.outWidth || imageOptions.outHeight > frameOptions.outHeight) {
            preScaleFactor = Math.max(imageOptions.outWidth / frameOptions.outWidth, imageOptions.outHeight / frameOptions.outHeight);
        }

        // Decode with inSampleSize
        BitmapFactory.Options scaleOptions = new BitmapFactory.Options();
        scaleOptions.inSampleSize = preScaleFactor;

        imageByteArrayInputStream = new ByteArrayInputStream(data);
        Bitmap preScaledBitmap = BitmapFactory.decodeStream(imageByteArrayInputStream, null, scaleOptions);
        System.gc();

        Bitmap finalBitmap;

        // Scale factor for precise scaling
        // If the scaled image is not exactly the same size as the frame than resize it precisely
        if (preScaledBitmap.getWidth() != frameOptions.outWidth || preScaledBitmap.getHeight() != frameOptions.outHeight) {
            float scaleFactor = Math.max((float)((float)preScaledBitmap.getWidth() / (float)frameOptions.outWidth), (float)((float)preScaledBitmap.getHeight() / (float)frameOptions.outHeight));
            float scalePercentage = Math.min((float)((float)frameOptions.outWidth / (float)preScaledBitmap.getWidth()), (float)((float)frameOptions.outHeight / (float)preScaledBitmap.getHeight()));

            Matrix matrix = new Matrix();
            matrix.preScale(scalePercentage, scalePercentage);

            // If the capture width for the source is bigger than the actual width of the source, then set is to the max of the actual source width
            int sourceCaptureWidth = (int)(frameOptions.outWidth * scaleFactor);
            if (sourceCaptureWidth > preScaledBitmap.getWidth()) {
                sourceCaptureWidth = preScaledBitmap.getWidth();
            }

            // Same as above but than for the height
            int sourceCaptureHeight = (int)(frameOptions.outHeight * scaleFactor);
            if (sourceCaptureHeight > preScaledBitmap.getHeight()) {
                sourceCaptureHeight = preScaledBitmap.getHeight();
            }

            finalBitmap = Bitmap.createBitmap(preScaledBitmap, 0, 0, sourceCaptureWidth, sourceCaptureHeight, matrix, true);

            preScaledBitmap.recycle();
            preScaledBitmap = null;

希望这可以帮助到你。

实际上,我在我的getView函数中实现了延迟加载,并且目前它可以正常工作而没有任何错误。我猜问题的根源在于我在getView函数中解码流,这里分配了太多的内存。无论如何,还是感谢你的回答!如果它仍然给我带来错误,我会尝试你的解决方案。 - Android-Droid
没问题,我很高兴你解决了它。 - Maikel Bollemeijer
我想向你致敬。几年前,我发明了自己的例程来完成同样的事情,但我更喜欢你的。非常出色。虽然最终我从Leonardo Arango Baena的答案中“借鉴”了Android提供的“hints”,这几乎就像作弊! - RayHaque

2
从这里获取信息:http://www.memofy.com/memofy/show/1008ab7f2836ab7f01071c2dbfe138/outofmemory-exception-when-decoding-with-bitmapfactory 尝试以下方法:
BitmapFactory.Options o = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];

Bitmap bitmapImage = BitmapFactory.decodeFile(path, options);

改为:

BitmapFactory.Options o = new BitmapFactory.Options();
    final int REQUIRED_SIZE=300*1024;

因此,在使用BitmapFactory.decodeFile()之前,创建一个16kb的字节数组,并将其传递给解码过程中的临时存储。

希望这有所帮助!参考:Strange out of memory issue while loading an image to a Bitmap object


1

使用recycle()。它将释放与此位图关联的本地对象,并清除对像素数据的引用。

 Bitmap ops = BitmapFactory.decodeStream(cis,null,o2);
        ((ImageView) convertView.findViewById(R.id.imgView)).setImageBitmap(ops);
        cis.close();
        fis.close();
        ops.recycle();
        System.gc();

当我尝试使用recycle时,它抛出了这个异常:java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@40627090, 640x960。 - Android-Droid
在 finally 块中可以使用 recycle()。尝试一下吧。 - Sujit

0

你正在运行哪个版本的Android?如果在Honeycomb或更高版本上运行,您应该能够使用Eclipse内存分析器查看内存使用情况。

话虽如此,您需要在位图不再需要或显示时立即调用recycle()(这是Sujit答案的问题所在)。换句话说,如果位图离开屏幕,最好将其回收(),然后在重新进入视图时再次加载它。否则,该位图会一直占用内存。

要做到这一点,请在ImageView上调用getDrawable(),在ImageView上调用setImageDrawable(null),然后将drawable转换为BitmapDrawable并回收其中的位图。

有关Android 3.0之前位图内存工作方式的更多信息,您可以查看我在此问题上发布的帖子:http://code.google.com/p/android/issues/detail?id=8488#c80


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