13

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

这是我的代码:

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 在此处查看此解决方案后,它适用于所需尺寸 300,但我所需尺寸为 800,因此我仍然收到错误消息。

4

10 回答 10

36

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

当我使用我的设备(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
于 2013-03-13T09:04:17.660 回答
4

这是用户在玩大位图时通常面临的一个常见问题,并且在现场讨论了很多问题,这里这里这里这里等等,即使用户无法操作确切的解决方案。

我偶然发现了一个库,它可以流畅地管理位图和我在下面列出的其他链接。希望这可以帮助!

冰沙图书馆

Android-BitmapCache

Android-Universal-Image-Loader OutOfMemoryError: bitmap size超出VM预算的解决方案

于 2013-03-07T06:05:34.517 回答
2

ARGB_8888使用更多内存,因为它需要 Alpha 颜色值,所以我的建议是按照此处RGB_565所述使用

注意:与 相比,质量会稍低ARGB_8888

于 2013-03-14T14:30:27.107 回答
1

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

结果:

  • 大多数设备的图形堆内存有限,大多数小型设备的整体应用程序限制为 16MB,而不仅仅是您的应用程序
  • 如果适用,使用 4 位或 8 位或 16 位位图
  • 尝试从头开始绘制形状,尽可能省略位图。
于 2013-03-07T06:09:15.603 回答
1

使用WebView动态加载任意数量的图像,它是使用 NDK(低级)构建的,因此没有 GDI 堆内存限制。它运行平稳快速:)

于 2013-03-07T06:29:34.033 回答
1

解码位图时的内存不足问题通常不会与您正在解码的图像大小相关联。当然,如果你尝试打开一个 5000x5000px 的图像,你会因为 OutOfMemoryError 而失败,但是对于 800x800px 的大小,它是完全合理的并且应该可以正常工作。

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

这是这篇文章的第一部分:

我想问题不在你的布局中,问题在你代码的其他地方。并且可能您正在某处泄漏上下文

这意味着您在不应该使用的组件中使用 Activity Context,从而防止它们被垃圾收集。因为组件通常由活动持有,这些活动不是 GC,您的 Java 堆会增长得非常快,您的应用程序有时会崩溃。

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

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

几年前我发现了一个关于使用 MAT 的好教程。也许现在有更好的。

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

Android - 内存泄漏还是?

android模拟器上的内存不足错误,但在设备上没有

于 2013-03-14T08:36:17.763 回答
1

您可能会保留以前的位图引用。我猜你多次执行此代码,但从未执行bitmap.recycle(). 内存将不可避免地耗尽。

于 2013-03-06T17:48:29.293 回答
0

看看这个视频。http://www.youtube.com/watch?v=_CruQY55HOk。不要按照视频中的建议使用 system.gc()。使用 MAT Analyzer 找出内存泄漏。我猜返回的位图太大导致内存泄漏。

于 2013-03-06T17:47:09.290 回答
0

看来您有大图像要显示。

您可以下载图像并保存到 sdcard(示例),然后您可以使用 代码显示 sdcard 中的图像。

于 2013-03-06T17:50:25.873 回答
-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 Android and error in loading images to gridview android

于 2013-03-15T13:28:24.783 回答