在onDestroy()中调用recycle()会出现“无法绘制已回收的位图”的错误。

5

我有两个活动:MainActivityActivity2

MainActivity 只是通过 Intent 打开第二个活动。

Activity2 返回到 MainActivity 时,我按下“返回”按钮。

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

  • 打开应用程序:出现MainActivity
  • 启动Intent:出现Activity2
  • 按下“返回”按钮:出现MainActivity
  • 启动Intent:我的应用程序由于以下错误而崩溃:

    IllegalArgumentException: Cannot draw recycled bitmaps

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)设备上发生。

  • 问题是否与Activities堆栈有关?
  • GC是否试图太晚释放位图的内存?如果是这种情况,如何彻底销毁Activity及其所有内容?
  • 这两个Android版本之间有区别吗?
  • 还是我漏掉了什么/做错了什么?

你尝试过我下面给出的答案了吗? - Rahul Khurana
如果 mapImageViews 不是静态的,那么将所有内容置空就没有意义了,更像是一种仪式而非真正的清理。 - Miha_x64
@Miha_x64,您能否更好地解释一下您的评论? - user2342558
3个回答

2
尝试将您的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();
}

另外可以参考这个链接:如何有效地重复利用图片


那么,super.onDestroy() 会创建那些 ImageView 的其他引用吗? - user2342558
@user2342558 不是的,当super被调用时,它会销毁与活动/片段相关联的所有视图引用。 - Rahul Khurana
如果是真的,为什么在Android 6.0设备上它可以正常工作而没有异常呢? - user2342558
@user2342558,这是由于内存管理引起的。请查看此处链接:https://dev59.com/c23Xa4cB1Zd3GeqPdVEs - Rahul Khurana
@user2342558 是的,某种程度上你是正确的。随着操作系统版本的增加,它们正在减少每个应用程序所分配的内存限制。最近在Android 10中,他们引入了Scoped存储作为额外的步骤。 - Rahul Khurana
显示剩余3条评论

1
实际上,我认为您不必手动完成此操作。 如果Activity2只有一张已经调整大小的图像,并且您确实面临内存问题,则我认为回收很难起到很大作用。 根据这份文档,仅推荐使用API级别低于10的recycle(),而用户比例相当小。 在Android 2.3.3(API级别10)及以下版本中,建议使用recycle()。https://developer.android.com/topic/performance/graphics/manage-memory 我想推荐使用第三方图像库,因为它们可以使您免于执行这些无意义的操作,让您专注于应用程序的更重要部分。

文档建议在Android API级别<=10时使用recycle(),但不建议在API> 10中使用。在许多其他SO答案中,建议按照我的方式使用recycle()来释放位图资源,当GC执行时。 - user2342558
是的,当API > 10时它不会显示推荐或不推荐,但我认为如果它只对早期GC有益,那么这并不那么重要(如果对所有版本都很重要,它将不会指示“在Android 2.3.3(API级别10)及更低版本中”)。而且它需要大量样板代码,难以阅读和维护。如果我们可以利用其他库来做到这一点,为什么不呢?^^ - Jintin

1

根据 recycle 的文档

位图被标记为“已死”,这意味着如果调用 getPixels() 或 setPixels() 方法将抛出异常,并且不会绘制任何内容。此操作无法撤销,因此只有在确定没有进一步使用位图时才应调用它。

我看不到你是如何将你的 bitmaps 分配给你的 ImageView 的,但我假设当你在已经回收它们的情况下再次启动意图时,你正在尝试重复使用位图。 我只遇到了一个异常,即当我使用 android:src= 时。 如果我使用以下代码在 oncreate 中设置 ImageView 位图,则在不抛出异常的情况下在您列出的所有目标上运行良好。

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

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


如果您不想使用第三方库,那么可以尝试使用https://developer.android.com/reference/java/lang/ref/WeakReference来处理具有LRU或磁盘缓存的位图,如https://developer.android.com/topic/performance/graphics/cache-bitmap中所述。 - Susheel Tickoo
@MatthewWilliams 我刚刚测试了相同的行为:如果我删除 android:src,则不会发生异常。现在,你知道为什么吗?我想这是因为 setContentView 在定义了 android:src 的 ImageView 上调用了 getPixels()/setPixels(),但它被回收了;而在 oncreate 中通过代码定义新位图时,这些方法不会被调用。这不是真的吗? - user2342558
是的,我认为你说得对。虽然我不能百分之百确定,但似乎在再次启动活动时,使用android:srcImageView分配的Drawable(如果已回收)不会被重新创建。我假设Drawable仅在程序启动时分配一次? - Matthew Williams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接