title: Asynchronous image loading in ListView: old images shown
body: <p>I have a <code>ListView</code> with the following adapter:</p>
  
  <pre><code>private class ListAdapter extends BaseAdapter {
          private List&lt;Item&gt; items;
          private ImageCache imageCache;
  
          public ListAdapter(List&lt;Item&gt; 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;
          }
      }
  </code></pre>
  
  <p>Below is my <code>ImageCache</code> class. It relies on <code>android.util.LruCache</code> and Jake Wharton's <a href="http://jakewharton.com/disklrucache/" rel="nofollow">DiskLruCache</a>; If item is not found in memory and disk cache, it is downloaded using AsyncTask.</p>
  
  <pre><code>private Map&lt;String,WeakReference&lt;ImageView&gt;&gt; references;
  
  //...
  public void load(String imageUrl, ImageView imgView) {
      final String key=getKey(imageUrl);
      references.put(key, new WeakReference&lt;ImageView&gt;(imgView)); 
      this.get(imageUrl, new OnImageLoadedListener() {
          public void onImageLoaded(Bitmap image) {
              WeakReference&lt;ImageView&gt; imgViewRef=references.get(key);
              if(imgViewRef!=null &amp;&amp; imgViewRef.get()!=null) {
                  imgViewRef.get().setImageBitmap(image);
              } else {
                  Log.d("Set image", "Dead reference! setting bitmap cancelled");
              }
          }
      });
  }
  </code></pre>
  
  <p>As you see, I'm trying to use <code>WeakReference</code> to my <code>ImageView</code>s to set image only if ImageView is still alive and to avoid leaking view references from <code>ListView</code>. However, This doesn't seem to work. Images are loaded normally for the first few visible items, but when I start scrolling my <code>ListView</code>, I see incorrect images (the ones used in previous list items), and that's annoying. How can I avoid this?
  As soon as download finishes, image is replaced with correct one, but download might take a while.</p>
  
  <p>An obvious workaround is to hide <code>ImageView</code> until download is finished, and then show it, but is there any other way?</p>
  
  <hr>
  
  <p>EDIT: Some other methods of <code>ImageCache</code> class:</p>
  
  <pre><code>//used to track requests for each image URL, key is URL
  private Map&lt;String, Queue&lt;OnImageLoadedListener&gt;&gt; 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&lt;OnImageLoadedListener&gt;());
      }
  
      Queue&lt;OnImageLoadedListener&gt; imageListenersQueue=listeners.get(key);
      imageListenersQueue.add(listener);
  
      //check if there are other requests for the same image
      if (imageListenersQueue.size()&gt;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 &amp;&amp; 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&lt;OnImageLoadedListener&gt; imageListenersQueue=(LinkedList&lt;OnImageLoadedListener&gt;) listeners.get(key);
      while (!imageListenersQueue.isEmpty()) {
          imageListenersQueue.remove().onImageLoaded(bitmap);
      }
  }
  
  //and the mysterious getKey()
  private String getKey(String str) {
      return Integer.toHexString(str.hashCode());
  }
  </code></pre>
  
  <p>And here's the log output for ImageCache:</p>
  
  <p>First visible list items:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>When scrolling to the next item:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>Seems like the problem is not in the caching mechanism, since the only <code>load()</code> call for this item gives me correct image set exactly to requested <code>ImageView</code>. It's just that incorrect image is displayed until it is downloaded and set. </p>