【问题标题】:"Cannot draw recycled bitmaps" if I invoke recycle() inside onDestroy()如果我在 onDestroy() 中调用 recycle(),则“无法绘制回收的位图”
【发布时间】:2020-01-07 15:23:10
【问题描述】:

我有两个活动:MainActivityActivity2

MainActivity 只是通过 Intent 打开秒一个。

要从Activity2 返回MainActivity,我按下“返回”按钮。

当我执行这些步骤时,应用程序崩溃:

  • 打开App:出现MainActivity
  • 启动 Intent:Activity2 出现
  • 按下“返回”按钮:MainActivity 出现
  • 启动Intent:我的应用程序因为这个错误而崩溃:

    IllegalArgumentException:无法绘制回收位图

MainActivity.java:

Intent intent = new Intent(this, Activity2.class);
startActivity(intent);

Activity2.java:

@Override
public void onBackPressed() {
    super.onBackPressed();
}

@Override
protected void onDestroy() {
    super.onDestroy();

    for(Map.Entry<Integer, ImageView> entry : mapImageViews.entrySet()) {
        ImageView imageView = entry.getValue();
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if(bitmap != null) {
                bitmap.recycle();
            }
            bitmapDrawable = null;
            bitmap = null;
        }
        imageView.setOnClickListener(null);
        imageView.setImageDrawable(null);
        imageView.setImageBitmap(null);
        imageView = null;
        drawable = null;
    }
    mapImageViews.clear();
    mapImageViews = null;
}

由于应用程序使用高分辨率图像(已与BitmapFactoryinSampleSize 适配),为了避免内存泄漏,我在onDestroy() 方法中调用recycle()

正如我通过阅读大量 SO 答案和在网络上了解到的那样,在位图上调用 recycle() 可以让它们尽早被垃圾收集。

但许多其他帖子建议不要调用 recycle(),或者至少建议仅在您确定 Activity 中不再需要位图时才这样做,即在 onDestroy() 方法中。

现在我有点担心我学到了什么,因为如果我删除 recycle(),错误就不会再发生了。

错误发生在装有 Android 4.4.2 的设备上,但不会发生在装有 Android 6.0Nexus 7 (Android 5.1.1) 的设备上。

  • 活动堆栈有问题吗?
  • GC 是否试图释放位图的内存为时已晚?在这种情况下,如何彻底销毁 Activity 及其所有内容?
  • 这两个 Android 版本有什么区别吗?
  • 还是有什么我遗漏/错误的地方?

【问题讨论】:

  • 你试过我下面给出的答案了吗?
  • 如果 mapImageViews 不是静态的,那么将所有内容归零是没有意义的,更像是货物崇拜而不是实际的清理。
  • @Miha_x64 你能更好地解释你的评论吗?

标签: java android exception bitmap garbage-collection


【解决方案1】:

实际上我认为您不必手动执行此操作。

如果 Activity2 只有 1 张图片并且已经调整大小,我认为如果您真的面临内存问题,回收将无济于事。

根据这个文档,只有低于 10 的 API 级别才推荐使用 recycle(),而且用户的比例很小。 On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. https://developer.android.com/topic/performance/graphics/manage-memory

我想推荐使用第三方图片库,因为他们可以让你从做这些无意义的事情中解放出来,让你专注于应用程序中更重要的部分。

【讨论】:

  • 文档建议对 Android API 级别 10 一起使用。在其他许多 SO 答案中,建议在我在 GC 动作时释放位图资源的方式。
  • 是的,当 API > 10 时它不会显示推荐与否,但我认为这样做并不重要,如果它只对早期 GC 有益(如果它对所有版本都很重要,它不会表明On Android 2.3.3 (API level 10) and lower)。而且它需要大量的样板代码,并且也难以阅读和维护。如果我们可以利用其他库来做到这一点,为什么不呢? ^^
【解决方案2】:

根据文档进行回收

位图被标记为“死”,这意味着它会抛出异常,如果 getPixels() 或 setPixels() 被调用,并且不会绘制任何内容。这 操作不能反转,所以只有在你是 确保位图没有其他用途。

我看不出您是如何将您的bitmaps 分配给您的ImageView,但我认为当您在位图为recycled 之后再次启动意图时,您正试图重用这些位图。如果我使用android:src=,我只会遇到异常。如果我在oncreate 中使用以下内容设置ImageView 位图,它在您列出的所有目标上运行良好,不会引发异常。

imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.lake_park));

我强烈推荐使用 glide 来处理图像。 https://github.com/bumptech/glide

【讨论】:

  • 如果您不想使用第 3 方库,请尝试将 developer.android.com/reference/java/lang/ref/WeakReference 用于具有 LRU 或磁盘缓存的位图,如 developer.android.com/topic/performance/graphics/cache-bitmap 中所述
  • @MatthewWilliams 我刚刚测试了相同的行为:如果我删除了android:src,则不会发生异常。现在,你知道为什么了吗?我想这是因为 setContentView 在使用 android:src 定义的 ImageView 上调用 getPixels()/setPixels() 但它被回收了;而是通过oncreate 中的代码定义新位图@ 这些方法不会被调用。不是吗?
  • 是的,我认为你说的是​​对的。我不能肯定地说,但是当您再次开始活动时,分配给带有android:srcImageViewDrawable 似乎不会重新创建(如果被回收)。我假设Drawable 在程序开始时只分配一次?
【解决方案3】:

尝试更改您的 onDestroy 方法,如下所示

@Override
protected void onDestroy() {
    for(Map.Entry<Integer, ImageView> entry : mapImageViews.entrySet()) {
        ImageView imageView = entry.getValue();
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if(bitmap != null) {
                bitmap.recycle();
            }
            bitmapDrawable = null;
            bitmap = null;
        }
        imageView.setOnClickListener(null);
        imageView.setImageDrawable(null);
        imageView.setImageBitmap(null);
        imageView = null;
        drawable = null;
    }
    mapImageViews.clear();
    mapImageViews = null;

    super.onDestroy();
}

另请参阅:How to recycle and reuse images 的有效方式。

【讨论】:

  • 我为什么要这样做?
  • 因为您在回收位图之前调用了超级方法,并且您从调用超级 onDestroy 时销毁的 ImageView 中获取了图像参考。
  • 那么,super.onDestroy() 创建了这些 ImageViews 的其他引用?
  • @user2342558 不,当超级被调用时,它会破坏所有附加到活动/片段的视图引用
  • 如果是真的,为什么在 Android 6.0 设备上它可以毫无例外地工作?
猜你喜欢
  • 2015-02-09
  • 1970-01-01
  • 2013-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多