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

Android BitmapFactory.decodeStream内存不足,尽管使用了减少的样本大小,android,bitmap,out-of-memory,Android,Bitmap,Out Of Memory,我读过很多关于解码位图时内存分配问题的相关文章,但即使使用了官方网站提供的代码,我仍然无法找到以下问题的解决方案 这是我的密码: public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] bu

我读过很多关于解码位图时内存分配问题的相关文章,但即使使用了官方网站提供的代码,我仍然无法找到以下问题的解决方案

这是我的密码:

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); 
在我看来,3.2MB足够小,可以分配。我哪里做错了?我怎样才能解决这个问题

编辑


在N-Joy研究了这个解决方案后,它可以正常工作,所需尺寸为300,但我所需的尺寸为800,因此我仍然得到了错误。

看一下这个视频。不要按照视频中的建议使用system.gc()。使用MAT分析仪查找内存泄漏。返回的位图太大,可能导致内存泄漏

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

您似乎有大图像要显示

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

这是用户在玩大型位图时通常会遇到的一个常见问题,尽管用户无法操作精确的解决方案,但在网站、和上讨论了许多问题

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


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

结果:

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

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

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

当我用我的设备(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个像素图像

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

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

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

如果您的设备内存不足,且图像容量为3.2MB,则可能是因为您在应用程序中的某个位置泄漏了上下文

这是第一部分:

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

这意味着您在不应该使用的组件中使用活动上下文,从而防止它们被垃圾收集。因为这些组件通常由活动持有,所以这些活动不是GC,您的java堆将快速增长,并且您的应用程序有时会崩溃

正如Raghunandan所说,您必须使用MAT来查找哪个活动/组件被保存,并删除上下文泄漏

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

几年前我发现了一个。也许现在有更好的

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


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


注意:与
ARGB_8888
相比,质量稍低,我之前也遇到过同样的问题。。我使用这个函数来管理它,你可以根据需要的宽度和高度来获取比例

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;
}
私有位图解码文件(FileInputStream f)
{
尝试
{
//解码图像大小
BitmapFactory.Options o=新的BitmapFactory.Options();
o、 inJustDecodeBounds=true;
解码流(f,null,o);
//找到正确的刻度值。它应该是2的幂。
所需的最终int_尺寸=70;
内部宽度=o.向外宽度,高度=o.向外高度;
int标度=1;
while(true)
{

如果(width_tmp/2即使我删除它也不能解决问题,有没有办法请告诉我我被卡住了很长时间删除也不能解决问题。要解决你的问题,请使用mat分析器找出位图占用了多少内存。我猜它占用了太多空间,这会导致内存泄漏。是的,它占用了太多内存,但如何解决这个问题呢大问题压缩应该减少一些内存使用。但是如果它仍然很大,导致内存泄漏,我恐怕什么也做不了。尝试压缩位图。如何压缩它?它会降低质量吗?每当你使用位图时,你都应该回收它。我无法回答,因为我不知道你在使用什么或如何使用它。是的,我不知道ried了您的解决方案,但仍然不走运:(我还缺少什么吗?假设
calculateInSampleSize()
re,这将使用与其当前实现完全相同的内存量
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;
}