ImageLoader.loadImage()同时加载相同url失败分析

现象
多处同时调用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
    }
});

你可能感兴趣的:(Android开发)