android - 使用 Google 的 ImageWorker 在 ListView 中加载图像导致 java.lang.RuntimeException

标签 android caching memory-management android-listview bitmap

最近,我开始使用 Google ImageWorker 类在后台线程上加载位图。此类处理所有事情,包括将位图放入 ImageView。 Google 在此处讨论此类(以及图像处理和缓存的辅助类):http://developer.android.com/training/displaying-bitmaps/process-bitmap.html

在我的例子中,ImageView 是 ListView 中列表项的一部分。这是我的 ArrayAdapter 上 getView 的有趣部分:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    View view = convertView == null ? inflater.inflate(R.layout.nodelist_item, null) : convertView;
    NodeHolder holder = getHolder(view);

    Node node = mList.get(position);
    holder.txtTitle.setText(node.Title);
    //Setting the rest of the different fields ...

    //And finally loading the image
    if(node.hasArt())
        worker.loadImage(node.ImageID, holder.imgIcon);
}

完整的 getView 方法可以在这里看到:http://pastebin.com/CJbtVfij

worker 对象是 ImageWorker 的一个非常简单的实现:

public class NodeArtWorker extends ImageWorker {

    protected int width;
    protected int height;

    public NodeArtWorker(Context context) {
        super(context);
        addImageCache(context);
        //Code for getting the approximate width and height of the ImageView, in order to scale to save memory.
    }

    @Override
    protected Bitmap processBitmap(Object data) {
        Bitmap b = getBitmap(data);
        if(b == null) return null;
        return Utils.resizeBitmap(b, width, height);
    }

    protected Bitmap getBitmap(Object data) {
        //Downloads the bitmap from a server, and returns it.
    }
}

这很好用,现在性能比以前好很多。但是,如果我更改列表中某些项目的 ImageID,并调用 adapter.notifyDataSetChanged() 来重建 View (从而开始加载新的位图),我会得到一个 RuntimeException:

0   java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@41adf448
1   at android.graphics.Canvas.throwIfRecycled(Canvas.java:1058)
2   at android.graphics.Canvas.drawBitmap(Canvas.java:1159)
3   at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:440)
4   at android.widget.ImageView.onDraw(ImageView.java:1025)
5   at android.view.View.draw(View.java:14126)

完整的堆栈跟踪可以在下面的 pastebin 条目中看到,但可能没有意思,因为它是典型的 Android View 层次结构重绘堆栈跟踪:http://pastebin.com/DsWcidqw

我知道位图正在被回收,但我不明白在哪里以及可以做什么。由于我的代码直接来自 Google,据我所知,这是推荐的这样做方式,我很困惑。

最佳答案

在 Android 上,Bitmap 可以回收以供以后重新使用(比重新创建 Bitmap 快得多)。
Bitmap#recycle() 方法会将位图标记为已回收。
因此,如果您尝试将这样的回收位图设置为 ImageView 或在 Canvas 上绘制它,您最终会遇到以下异常:

java.lang.RuntimeException: Canvas: trying to use a recycled bitmap

您在问题中链接的官方演示是处理回收的Bitmaps。
它使用专用方法 hasValidBitmap() 并检查 Bitmap#isRecycled() 值:

private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}

所以你也需要这样做。当您在缓存中搜索 Bitmap 时,在将其应用于 ImageView 之前,请检查它是否被回收。如果不是可以直接设置,否则需要更新Bitmap。

要从回收的位图创建新的位图,您可以使用 Bitmap::createBitmap(Bitmap)方法。

您可以在 this page 上找到更多详细信息.

关于android - 使用 Google 的 ImageWorker 在 ListView 中加载图像导致 java.lang.RuntimeException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25390551/

相关文章:

java - 使 'static' 类可观察

Android 服务未收到位置更新

c - 大维度值错误 - 运行时检查失败 #2 - 变量 'mat' 周围的堆栈已损坏

android - 在 MaterialCalendarView 中使用多个 DotSpan

java - 刷新 Activity 而不再次打开?

android - 有什么方法可以处理回收过程中的 View ?

angular - 在 Angular 中缓存图像

reactjs - React Query 似乎没有缓存

objective-c - Objective C UIImagePNGRepresentation 内存问题(使用 ARC)

ios - NSURLConnection 内存泄漏