【问题标题】:Asynchronous image loading in ListView: old images shownListView 中的异步图像加载:显示旧图像
【发布时间】:2012-12-04 17:56:13
【问题描述】:

我有一个带有以下适配器的ListView

private class ListAdapter extends BaseAdapter {
        private List<Item> items;
        private ImageCache imageCache;

        public ListAdapter(List<Item> item) {
            this.items=items;
            imageCache=ImageCache.getInstance();
        }

        //nothing special here
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;

            if (convertView == null) {
                convertView = getLayoutInflater().inflate(R.layout.listitem, parent, false);

                viewHolder = new ViewHolder();
                viewHolder.imgView = (ImageView) convertView.findViewById(R.id.imgview);
                viewHolder.txtView = (TextView) convertView.findViewById(R.id.txtview);

                convertView.setTag(viewHolder);
            } else  {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            Item item = getItem(position);

                    //this method is used to start download
            imageCache.load(item.getImageURL(), viewHolder.imgView);

            viewHolder.txtView.setText(item.getText());

            return convertView;
        }
    }

下面是我的ImageCache 课程。它依赖于android.util.LruCache 和 Jake Wharton 的DiskLruCache;如果在内存和磁盘缓存中找不到项目,则使用 AsyncTask 下载。

private Map<String,WeakReference<ImageView>> references;

//...
public void load(String imageUrl, ImageView imgView) {
    final String key=getKey(imageUrl);
    references.put(key, new WeakReference<ImageView>(imgView)); 
    this.get(imageUrl, new OnImageLoadedListener() {
        public void onImageLoaded(Bitmap image) {
            WeakReference<ImageView> imgViewRef=references.get(key);
            if(imgViewRef!=null && imgViewRef.get()!=null) {
                imgViewRef.get().setImageBitmap(image);
            } else {
                Log.d("Set image", "Dead reference! setting bitmap cancelled");
            }
        }
    });
}

如您所见,我正在尝试使用WeakReference 到我的ImageViews 来设置图像,仅当ImageView 仍然存在时,并避免从ListView 泄漏视图引用。但是,这似乎不起作用。前几个可见项目的图像正常加载,但是当我开始滚动我的ListView 时,我看到不正确的图像(以前列表项中使用的图像),这很烦人。我怎样才能避免这种情况? 下载完成后,图像会立即替换为正确的图像,但下载可能需要一段时间。

一个明显的解决方法是在下载完成之前隐藏ImageView,然后显示它,但还有其他方法吗?


编辑:ImageCache 类的一些其他方法:

//used to track requests for each image URL, key is URL
private Map<String, Queue<OnImageLoadedListener>> listeners;

//...

public void get(String imageUrl, OnImageLoadedListener listener) {
    final String key=this.getKey(imageUrl);
    Bitmap image;

    Log.d(LOG_TAG, "image "+key+" for "+listener.hashCode()+": requesting...");
    if (!listeners.containsKey(key)) {
        listeners.put(key, new LinkedList<OnImageLoadedListener>());
    }

    Queue<OnImageLoadedListener> imageListenersQueue=listeners.get(key);
    imageListenersQueue.add(listener);

    //check if there are other requests for the same image
    if (imageListenersQueue.size()>1) {
        Log.d(LOG_TAG, "image "+key+" for "+listener.hashCode()+": already requested...");
                    return;
    }

    //proceed only if this request is the first
            Log.d(LOG_TAG, "image "+key+" for "+listener.hashCode()+": proceeding...");
    image=memCache.get(key);
    if (image==null && diskCache!=null) {
        image=diskCache.getBitmap(key);

        if (image==null) {
            new BitmapDownloader(new OnImageLoadedListener() {
                public void onImageLoaded(Bitmap image) {
                    if (image!=null) {
                        memCache.put(key, image);
                        diskCache.put(key, image);
                    }
                    post(key, image);
                                            Log.d(LOG_TAG, "image "+key+", downloaded bitmap "+image.hashCode());
                }
            }).execute(imageUrl);
        } else {
            memCache.put(key, image);
                            Log.d(LOG_TAG, "image "+key+" found in diskcache");
            post(key, image);
        }
    } else {
                    Log.d(LOG_TAG, "image "+key+" found in memcache");
        post(key, image);
    }
}

private void post(String key, Bitmap bitmap) {
    //will now pass bitmap to all listeners
    LinkedList<OnImageLoadedListener> imageListenersQueue=(LinkedList<OnImageLoadedListener>) listeners.get(key);
    while (!imageListenersQueue.isEmpty()) {
        imageListenersQueue.remove().onImageLoaded(bitmap);
    }
}

//and the mysterious getKey()
private String getKey(String str) {
    return Integer.toHexString(str.hashCode());
}

这是 ImageCache 的日志输出:

第一个可见的列表项:

12-05 12:30:06.036: D/ImageCache(21138): image 40bc8de0 for 1104135776: requesting...
12-05 12:30:06.036: D/ImageCache(21138): image 40bc8de0 for 1104135776: proceeding...
12-05 12:30:06.071: D/ImageCache(21138): image 6589e90 for 1103929688: requesting...
12-05 12:30:06.071: D/ImageCache(21138): image 6589e90 for 1103929688: proceeding...
12-05 12:30:07.251: D/ImageCache(21138): image 40bc8de0 for 1104135776 ready, setting bitmap 1104282656
12-05 12:30:07.255: D/ImageCache(21138): image 40bc8de0, downloaded bitmap 1104282656
12-05 12:30:08.407: D/ImageCache(21138): image 6589e90 for 1103929688 ready, setting bitmap 1104663160
12-05 12:30:08.411: D/ImageCache(21138): image 6589e90, downloaded bitmap 1104663160

滚动到下一项时:

12-05 12:30:44.669: D/ImageCache(21138): image 2e762e6c for 1104686624: requesting...
12-05 12:30:44.673: D/ImageCache(21138): image 2e762e6c for 1104686624: proceeding...
12-05 12:30:45.997: D/ImageCache(21138): image 2e762e6c for 1104686624 ready, setting bitmap 1104089312
12-05 12:30:45.997: D/ImageCache(21138): image 2e762e6c, downloaded bitmap 1104089312

似乎问题不在于缓存机制,因为对该项目的唯一load() 调用为我提供了与请求的ImageView 完全一致的正确图像集。只是在下载和设置之前显示不正确的图像。

【问题讨论】:

    标签: android android-widget android-listview listadapter


    【解决方案1】:

    这里需要考虑两件事。首先,当您在缓存中找不到目标位图时,您应该将ImageView 源设置为某种加载可绘制对象。这很简单。

    其次,您需要考虑视图回收。您的代码中没有任何内容可以阻止您开始下载ImageView 的情况,但在调用onImageLoaded() 之前,适配器中的视图被回收以显示其他图像。然后,下载完成,导致显示完全错误的图像。

    对此的典型解决方案是在您的ImageView 上调用setTag(),并设置它应该加载的URL。在 load() 方法的开头执行此操作。然后,您可能想要为 URL 添加一个额外的参数到onImageLoaded()。检查此 URL 是否与 ImageView 标签匹配,如果 URL 匹配,则仅调用 setImageBitmap()

    【讨论】:

    • 我尝试将 url 设置为 ImageView 的标签并检查它(将 URL 作为 onImageLoaded() 的参数传递),但没有任何区别。 URL 始终匹配,我的日志中从未收到“无效参考!设置位图已取消”消息。无论如何,谢谢你的回复!我在帖子中添加了一些细节,你能看一下吗?
    • 当您知道需要下载图像时,将ImageView 源设置为某种进度图像,或者像您说的那样隐藏它。
    • 我最终在下载之前将null 设置为ImageView。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多