现象
多处同时调用loadImage方法加载相同url,可能会导致部分调用失效,即图片加载不出来。比较普遍的场景是在Adapter的ViewHolder中加载相同的url。ImageLoader相关log如下:
07-02 17:14:22.754 30344-30486/ D/ImageLoader: ImageAware is reused for another image. Task is cancelled. [https://stage.meitudata.com/meitupu/c1bf59d1-dbda-4f65-a5de-14e667078710.png_720x1184]
07-02 17:14:22.755 30344-30483/ D/ImageLoader: ImageAware is reused for another image. Task is cancelled. [https://stage.meitudata.com/meitupu/c1bf59d1-dbda-4f65-a5de-14e667078710.png_720x1184]
07-02 17:14:22.775 30344-30344/ D/ImageLoader: ImageAware is reused for another image. Task is cancelled. [https://stage.meitudata.com/meitupu/c1bf59d1-dbda-4f65-a5de-14e667078710.png_720x1184]
原因
后调用loadImage时,如果有缓存,那么就会走缓存;如果没有缓存(前面的load task还未完成),load task内部会根据ImageAware判断是否是相同的任务,如果是,则取消task。
// LoadAndDisplayImageTask.java
private boolean isViewReused() {
String currentCacheKey = this.engine.getLoadingUriForView(this.imageAware);
boolean imageAwareWasReused = !this.memoryCacheKey.equals(currentCacheKey);
if(imageAwareWasReused) {
L.d("ImageAware is reused for another image. Task is cancelled. [%s]", new Object[]{this.memoryCacheKey});
return true;
} else {
return false;
}
}
// ImageLoaderEngine.java
String getLoadingUriForView(ImageAware imageAware) {
return (String)this.cacheKeysForImageAwares.get(Integer.valueOf(imageAware.getId()));
}
从上面代码可以看出,判断主要依赖于imageAware.getId()方法。ImageAware是一个接口,表明image下载相关的信息,例如宽高大小等。主要有两个实现类:ViewAware和NonViewAware。从名字就可以看出,一个耦合具体View,一个与View无关。两者的getId实现方法如下:
// ViewAware.java
public int getId() {
View view = (View)this.viewRef.get();
return view == null?super.hashCode():view.hashCode();
}
// NonViewAware.java
public int getId() {
return TextUtils.isEmpty(this.imageUri)?super.hashCode():this.imageUri.hashCode();
}
ViewAware是我们在向指定imagview加载图片时ImageLoader内部使用到的。而我们在使用loadImage下载一张图片,通常并没有指定任何ImageAware,ImageLoader内部最终会使用NonViewAware创建一个。
public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
L.d("loadImage(Uri [%s]) , Options [%s] ", new Object[]{uri, options});
this.checkConfiguration();
if(targetImageSize == null) {
targetImageSize = this.configuration.getMaxImageSize();
}
if(options == null) {
options = this.configuration.defaultDisplayImageOptions;
}
NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
this.displayImage(uri, (ImageAware)imageAware, options, listener, progressListener);
}
至此,可以看到,loadImage方法创建的task,最终都会根据NonViewAware的uri来判断是否需要取消task,直接导致相同url加载不成功。
解决方案
手动设置NonViewAware,并且不传入uri参数,这样在做isViewReused()判断时,NonViewAware.getId()取的是super.hashCode(),而不是imageUri.hashCode(),成功避免load task被取消。
ImageSize imageSize = new ImageSize(0, 0);
NonViewAware nonViewAware = new NonViewAware(imageSize, ViewScaleType.FIT_INSIDE);
ImageLoader.getInstance().displayImage(url, nonViewAware, new SimpleImageLoadingListener(){
@Override
public void onLoadingComplete(String imageUri, View view, BaseBitmapDrawable loadedImage) {
// do something
}
});