尽管使用了减少的样本大小,但BitmapFactory.decodeStream内存不足

时间:2013-03-06 17:41:58

标签: android bitmap out-of-memory

我已经阅读了许多关于解码位图的内存分配问题的相关帖子,但即使使用了官方网站提供的代码,我仍然无法找到解决以下问题的方法。

这是我的代码:

public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = inputStream.read(buffer)) > -1) {
        baos.write(buffer, 0, len);
        }
        baos.flush();
        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is1, null, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inJustDecodeBounds = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeStream(is2, null, options);

    } catch (Exception e) {
        e.printStackTrace();

        return null;
    }
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final 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 width
        final 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;
    }


    return inSampleSize;
}

bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600); 

我在这行中得到“3250016字节分配的内存不足错误”:

return BitmapFactory.decodeStream(is2, null, options);

在我看来,3.2 MB足够小,可以分配。我哪里错了?我该如何解决这个问题?

修改

通过N-Joy查看此解决方案HERE后,它可以正常使用所需尺寸300,但我所需的尺寸为800,所以我仍然会收到错误。

10 个答案:

答案 0 :(得分:36)

方法 decodeSampledBitmapFromResource 不具有内存效率,因为它使用3个流:ByteArrayOutputStream baos,ByteArrayInputStream is1和ByteArrayInputStream is2,每个存储相同的流数据image (每个字节数组)。

当我使用我的设备(LG nexus 4)测试将SD卡上的2560x1600图像解码为目标尺寸800时,需要执行以下操作:

03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999

我们可以看到:分配太多内存(28.5 MB)只是为了解码4096000像素图像。

解决方案:我们读取InputStream并将数据直接存储到一个字节数组中,并使用此字节数组进行其余工作。
示例代码:

public Bitmap decodeSampledBitmapFromResourceMemOpt(
            InputStream inputStream, int reqWidth, int reqHeight) {

        byte[] byteArr = new byte[0];
        byte[] buffer = new byte[1024];
        int len;
        int count = 0;

        try {
            while ((len = inputStream.read(buffer)) > -1) {
                if (len != 0) {
                    if (count + len > byteArr.length) {
                        byte[] newbuf = new byte[(count + len) * 2];
                        System.arraycopy(byteArr, 0, newbuf, 0, count);
                        byteArr = newbuf;
                    }

                    System.arraycopy(buffer, 0, byteArr, count, len);
                    count += len;
                }
            }

            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(byteArr, 0, count, options);

            options.inSampleSize = calculateInSampleSize(options, reqWidth,
                    reqHeight);
            options.inPurgeable = true;
            options.inInputShareable = true;
            options.inJustDecodeBounds = false;
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;

            int[] pids = { android.os.Process.myPid() };
            MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
            Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);

            return BitmapFactory.decodeByteArray(byteArr, 0, count, options);

        } catch (Exception e) {
            e.printStackTrace();

            return null;
        }
    }

进行计算的方法:

public void onButtonClicked(View v) {
        int[] pids = { android.os.Process.myPid() };
        MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);

        long startTime = System.currentTimeMillis();

        FileInputStream inputStream;
        String filePath = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/test2.png";
        File file = new File(filePath);
        try {
            inputStream = new FileInputStream(file);
//          mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
            mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
                    800);
            ImageView imageView = (ImageView) findViewById(R.id.image);
            imageView.setImageBitmap(mBitmap);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
                + " time = " + (System.currentTimeMillis() - startTime));
    }

结果:

03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917

答案 1 :(得分:4)

这是用户在玩大型位图时通常会遇到的常见问题,网站上讨论的问题很多,herehereherehere还有更多,即使用户无法操纵确切的解决方案。

我偶然发现了一个图书馆,它可以顺利地管理位图,以及我在下面列出的其他链接。希望这可以帮助!

smoothie-Library

Android-BitmapCache

Android-Universal-Image-Loader Solution for OutOfMemoryError: bitmap size exceeds VM budget

答案 2 :(得分:2)

ARGB_8888使用更多内存,因为它需要Alpha颜色值,因此我的建议是使用RGB_565 HERE

注意:与ARGB_8888相比,质量会低一些。

答案 3 :(得分:1)

您可能正在坚持以前的位图引用。我猜你正在多次执行这段代码,从不执行bitmap.recycle()。记忆将不可避免地耗尽。

答案 4 :(得分:1)

我在使用Bitmap内存方面遇到了很多问题。

结果:

  • 大多数设备的图形堆内存有限,大多数小型设备的整体应用限制为16MB,而不仅仅是您的应用
  • 使用4位或8位或16位位图(如果适用)
  • 尝试从头开始绘制形状,如果可能,请省略位图。

答案 5 :(得分:1)

使用 WebView 动态加载任意数量的图像,它是使用NDK(低级别)构建的,因此没有GDI堆内存限制。 它工作顺畅而快速:)

答案 6 :(得分:1)

解码位图时内存不足问题通常与您正在解码的图像大小无关。 当然,如果您尝试打开图像5000x5000px,您将失败并出现OutOfMemoryError,但是大小为800x800px,这是完全合理的,应该可以正常工作。

如果您的设备内存不足,带有3.2 MB的图像,可能是因为您在应用程序的某处泄露了上下文。

这是this post的第一部分:

  

我猜问题不在你的布局中,问题在其他地方   你的代码。并且可能是你在某处泄露了背景

这意味着你在不应该使用的组件中使用Activity Context,防止它们被垃圾收集。因为组件通常由活动保存,所以这些活动不是GC,并且您的Java堆将快速增长,并且您的应用程序将在某个时间崩溃。

正如Raghunandan所说,你将不得不使用MAT来找到所持有的Activity / Component并删除上下文泄漏。

我现在发现检测上下文泄漏的最佳方法是方向更改。 例如,多次旋转ActivityMain,运行MAT并检查是否只有一个ActivityMain实例。如果您有多个(与旋转更改一样多),则表示存在上下文泄漏。

我多年前发现了good tutorial on using MAT。也许现在有更好的一个。

关于内存泄漏的其他帖子:

Android - memory leak or?

Out of memory error on android emulator, but not on device

答案 7 :(得分:0)

看看这个视频。 http://www.youtube.com/watch?v=_CruQY55HOk。请勿使用视频中建议的system.gc()。使用MAT分析器查找内存泄漏。返回的位图太大导致内存泄漏我猜。

答案 8 :(得分:0)

您似乎要显示大图片。

您可以下载图片并保存到SD卡(example),然后您可以使用this 从SD卡显示图像的代码。

答案 9 :(得分:-1)

我之前也有同样的问题..我已经通过使用这个功能管理它,你可以得到你所需的宽度和高度。

private Bitmap decodeFile(FileInputStream f)
{
    try
    {
        //decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(f,null,o);

        //Find the correct scale value. It should be the power of 2.
        final int REQUIRED_SIZE=70;
        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;
        return BitmapFactory.decodeStream(f, null, o2);
    } 
    catch (FileNotFoundException e) {}
    return null;
}

并参考Memory Leak Error Androiderror in loading images to gridview android