【问题标题】:BitmapFactory.decodeResource and inexplicable Out of MemoryBitmapFactory.decodeResource 和莫名其妙的Out of Memory
【发布时间】:2016-07-05 13:54:21
【问题描述】:

解码可绘制图像资源 960x926px jpg,分配 3555856 字节时出现奇怪的内存不足错误。 图像仅放置在 drawable-xxhdpi (3x) 中,我使用的是 hdpi (1.5x) 设备。 两个问题:

  • 为什么堆中有足够的可用内存却出现错误?

  • 分配给 hdpi 设备应该是 ((960/2) x (926/2)) x 4 = 888960 字节(不是 3555856)?

谁能解释一下?

注意:问题是为什么要获得 3.5MB 分配的 OOM,同时拥有 22.5MB 可用内存(请参阅日志)

03-18 17:30:15.050 32750-32750/? D/dalvikvm: GC_FOR_ALLOC 已释放 10809K,49% 空闲 23735K/46087K,暂停 89ms,总共 89ms

03-18 17:30:15.050 32750-32750/? I/dalvikvm-heap:强制收集 3555856 字节分配的软引用数

03-18 17:30:15.160 32750-32750/? D/dalvikvm:GC_BEFORE_OOM 释放 29K, 49% 免费 23705K/46087K,暂停 103ms,共 103ms

03-18 17:30:15.160 32750-32750/? E/dalvikvm-heap: 内存不足 3555856 字节分配

03-18 17:30:15.160 32750-32750/?我/dalvikvm:“主要”prio=5 tid=1 可运行

03-18 17:30:15.160 32750-32750/?我/dalvikvm: |组=“主”sCount=0 dsCount=0 obj=0x418fc6a0 self=0x4010c008

03-18 17:30:15.160 32750-32750/?我/dalvikvm: | sysTid=32750 不错=1 sched=0/0 cgrp=apps 句柄=1075251280

03-18 17:30:15.160 32750-32750/?我/dalvikvm: | schedstat=( 0 0 0 ) utm=3807 stm=859 核心=0

03-18 17:30:15.160 32750-32750/?我/dalvikvm:在 android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)

03-18 17:30:15.160 32750-32750/?我/dalvikvm:在 android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:636)

03-18 17:30:15.160 32750-32750/?我/dalvikvm:在 android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:484) 03-18 17:30:15.160 32750-32750/?我/dalvikvm:在 android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:512)

03-18 17:30:15.160 32750-32750/?我/dalvikvm:在 android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:542)

【问题讨论】:

  • 如果你使用android模拟器,你在堆中设置了多少内存?
  • @Khang .NT 4 我使用的是 48M 堆大小的真实设备
  • “为什么我在堆中有足够的空闲内存时得到错误?” -- 堆上没有足够大的块来满足您的请求。 Dalvik 垃圾收集器不是压缩垃圾收集器,这意味着堆会变得碎片化。 ART 的垃圾收集器将压缩堆,以便分配更大的块,但仅限于应用程序在后台时。
  • @CommonsWare 有没有办法检查堆的分段状态?
  • 很遗憾,没有。

标签: android android-image android-bitmap bitmapfactory android-memory


【解决方案1】:

1)如果 hdpi 文件夹中没有较小的版本,它将使用最接近的匹配。因此,如果不存在 hdpi 或 drawable/ 版本,它将使用 xxhdpi。

2) 它不会自动缩放。它将以完整尺寸读取。

3)如果这会导致 OOM,一般来说您可能使用了过多的内存。

【讨论】:

  • 你的意思是OOM发生在设备内存中,而不是VM堆中?
【解决方案2】:

你得到 OOM 的原因是因为当图像被解码到内存中时,它的位图比图像分辨率需要更多的大小(几乎 4 次我不确定这个值)。

处理图像时要记住的几点:

  1. 永远不要在主线程上处理位图。在后台进行所有解码。
  2. 始终考虑要放置图像的屏幕大小或视图大小。例如,如果您的屏幕尺寸是 360X720(一些随机值),那么解码分辨率大于所需尺寸的全分辨率图像不是一个好主意(因为它将在主存储器中加载完整的位图)。因此,请务必在解码时进行采样。

所以尝试使用以下解决方案:

第 1 步:查找屏幕尺寸

取自here

Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;

第 1 步:创建一个异步任务来解码位图

如果您使用异步任务作为内部类,请考虑使用公共静态内部类(以解决内存泄漏问题)并保留要在其中加载图像的 imageview 的弱引用。还将您想要解码的图像资源或文件或流传递给构造函数。在下面的代码中,假设您要解码资源。还要传递在步骤 1 中计算的宽度和高度。

public static class BitmapDecodeTask extends AsyncTask<Void, Void, Bitmap> {
    //the reason to use a weak reference is to protect from memory leak issues.
    private WeakReference<Context> mContextReference;
    private WeakReference<ImageView> mImageViewReference;
    private int mResourceId;
    private int mRequiredWidth;
    private int mRequiredHeight;

    public BitmapDecodeTask(Context context, ImageView imageView, int resourceId, int width, int height) {
        this.mContextReference = new WeakReference<>(context);
        this.mImageViewReference = new WeakReference<>(imageView);
        this.mResourceId = resourceId;
        this.mRequiredWidth = width;
        this.mRequiredHeight = height;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {

        Context context = mContextReference.get();

        if(context != null) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), mImageResourceId, options);

            //set inSampleSize
            options.inSampleSize = calculateInSampleSize(options);

            //set inJustDecodeBounds = false;
            options.inJustDecodeBounds = false;

            //decode
            return BitmapFactory.decodeResource(getResources(), mImageResourceId, options);
        }

        return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        //check if imageview is available or not
        ImageView imageView = mImageViewReference.get();

        if(imageView != null && bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
    }

    public static int calculateInSampleSize(BitmapFactory.Options options) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > mRequiredHeight || width > mRequiredWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > mRequiredHeight 
                && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

参考资料:

Bitmaps

【讨论】:

  • 仔细阅读这个问题:没有内存泄漏,堆中有 49% 的空闲空间。
  • 这就是我在答案的第一行中所写的。每当您解码图像时,它都会转换为位图,该位图大约是图像尺寸的 4 倍。我在某处读到过它。所以我只是想告诉根据所需的宽度和高度进行解码。那就是做采样解码。在编写解决方案时,我也尝试使用所有最佳实践,这就是为什么提到内存泄漏和处理 UI 线程的原因。我并不是说您的代码导致内存泄漏。 (960*926)*4 大约等于 3555840。希望能回答您的问题:)
  • @CommonsWare 的理由也是正确的。刚才我看了他的评论。
  • @GPack 你的问题的最终结论是什么?
  • 对不起,我无法分配赏金,因为在我看来,所有的回复,即使它们非常有趣,也没有指向我的问题的主题:这就是为什么我在有足够的空闲时获得 OOM记忆。只有 CommonsWare 的评论集中在这个问题上,部分是 GabeSechen 关于 autoscale 的回复。
【解决方案3】:

位图在内存中的大小是宽度 x 高度 x 每个颜色的位数。我相信您没有使用任何特定选项,您使用的是每个像素 4 个字节(红色 1 个字节,绿色 1 个字节,蓝色 1 个字节,Alpha 1 个字节)。所以:960*926*4=3555840 字节。

关于您的 OOM:对我来说似乎不太常见的是:

强制为 3555856 字节分配收集 SoftReferences

您将分配的位图存储在哪里?应避免在 Android 上使用软引用。

【讨论】:

  • 是的,我知道 x4 字节规则:第二个问题是关于框架的 dpi-drawable 文件夹管理,而不是关于位图分配数学。在可能的 OOM 之前强制收集 SoftReferenced 可达对象是 VM 的通常的标准行为,这是释放内存的最后机会。这里的问题是有 49% 的可用内存。
  • 您是否在某个缓存中缓存位图?第二:你是如何解码位图的?提供代码行。
【解决方案4】:

按照 Gabe Sechan 的建议,我建议使用像 makeappicon 这样的网站,它允许您自动缩放任何太大的图像。这是一个有用的工具,尽管 Android 应该会为您处理这些扩展问题。

Mimmo Grottoli 对 ARGB 字节的看法是正确的,所以据我所知没有什么可担心的。

您最有可能收到此错误的原因是因为在创建(而不是破坏)位图时涉及到严重的内存泄漏,这是我不久前从一个 steg 项目中学到的艰难方法。

为了清理此内存,您可以覆盖 Activity/Fragment 的 onDestroy() 方法,或在必要时手动执行。

@Override
 public void onDestroy() {
    yourbitmap.recycle();
    yourbitmap = null;
    super.onDestroy();
 }

希望他对你有帮助。

【讨论】:

  • 没有泄漏,问题是为什么在拥有 22.5MB 可用内存的同时获得 3.5MB 分配的 OOM(参见上面的日志)
【解决方案5】:

最好使用 Glide 或 picasso 等库进行所有图像操作(解码、调整大小、下载等):

将路径替换为本地文件夹路径或服务器 url

依赖关系

compile 'com.github.bumptech.glide:glide:3.5.2'

使用 Glide 加载图片

Glide.with (context).load (path).asBitmap().into(imageView);

【讨论】:

    猜你喜欢
    • 2012-05-19
    • 1970-01-01
    • 2012-03-12
    • 2019-02-04
    • 2013-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多