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

当多个地方同时使用ImageLoader的loadImage()加载相同URL时,可能导致部分加载失败。问题在于,如果一个load任务还未完成,后续任务会依据缓存或判断是否为同一任务来决定是否取消。判断依据是ImageAware.getId(),对于NonViewAware,getId()返回的是uri的hashCode。解决方案是手动设置NonViewAware,不传uri参数,使getId()返回super.hashCode(),避免任务被错误取消。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现象
多处同时调用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
    }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值