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

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

现象
多处同时调用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
    }
});
CompileCommand: exclude com/intellij/openapi/vfs/impl/FilePartNodeRoot.trieDescend bool exclude = true 2025-05-13 18:36:18,655 [ 1014] WARN - #c.i.o.v.n.p.l.VfsLog - VFS Log version differs from the implementation version: log null vs implementation -43 2025-05-13 18:36:19,191 [ 1550] WARN - #c.i.i.u.l.LafManagerImpl - VersionControl.Log.Commit.rowHeight = null in IntelliJ; it may lead to performance degradation 2025-05-13 18:36:19,391 [ 1750] WARN - #c.i.o.a.i.ActionManagerImpl - keymap "VSCode" not found PluginDescriptor(name=Terminal, id=org.jetbrains.plugins.terminal, descriptorPath=plugin.xml, path=C:\IntelliJ IDEA 2023.1.2\plugins\terminal, version=231.9011.34, package=null, isBundled=true) Start Failed Internal error. Please refer to https://jb.gg/ide/critical-startup-errors org.h2.mvstore.MVStoreException: File corrupted in chunk 63, expected map id 2, got 8 [2.2.219/6] at org.h2.mvstore.DataUtils.newMVStoreException(DataUtils.java:996) at org.h2.mvstore.Page.read(Page.java:612) at org.h2.mvstore.Page.read(Page.java:262) at org.h2.mvstore.FileStore.readPage(FileStore.java:1957) at org.h2.mvstore.MVStore.readPage(MVStore.java:1021) at org.h2.mvstore.MVMap.readPage(MVMap.java:632) at org.h2.mvstore.MVMap.readOrCreateRootPage(MVMap.java:657) at org.h2.mvstore.MVMap.setRootPos(MVMap.java:642) at org.h2.mvstore.MVStore.openMap(MVStore.java:479) at org.h2.mvstore.MVStore.openMap(MVStore.java:426) at com.intellij.ui.svg.SvgCacheManagerKt$getMap$1.invoke(SvgCacheManager.kt:232) at com.intellij.ui.svg.SvgCacheManagerKt$getMap$1.invoke(SvgCacheManager.kt:230) at com.intellij.ui.svg.SvgCacheManagerKt.getMap$lambda$0(SvgCacheManager.kt:230) at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) at com.intellij.ui.svg.SvgCacheManagerKt.getMap(SvgCacheManager.kt:230) at com.intellij.ui.svg.SvgCacheManagerKt.access$getMap(SvgCacheManager.kt:1) at com.intellij.ui.svg.SvgCacheManager.loadFromCache(SvgCacheManager.kt:69) at com.intellij.util.SVGLoader.loadFromClassResource(SVGLoader.kt:95) at com.intellij.util.ImageLoaderKt.loadByDescriptorWithoutCache(ImageLoader.kt:580) at com.intellij.util.ImageLoaderKt.loadByDescriptor(ImageLoader.kt:542) at com.intellij.util.ImageLoaderKt.access$loadByDescriptor(ImageLoader.kt:1) at com.intellij.util.ImageLoader.loadImage(ImageLoader.kt:86) at com.intellij.util.ImageLoader.loadImage$default(ImageLoader.kt:72) at com.intellij.openapi.ui.ImageDataByPathLoader.loadImage(ImageDataByPathLoader.kt:94) at com.intellij.openapi.util.CachedImageIcon.loadImage$intellij_platform_util_ui(CachedImageIcon.kt:269) at com.intellij.openapi.util.ScaledIconCache.getOrScaleIcon(ScaledIconCache.kt:46) at com.intellij.openapi.util.CachedImageIcon.getRealIcon$intellij_platform_util_ui(CachedImageIcon.kt:151) at com.intellij.openapi.util.CachedImageIcon.getIconWidth(CachedImageIcon.kt:104) at com.intellij.ui.AppUIUtil.scaleIconToSize(AppUIUtil.java:188) at com.intellij.ui.AppUIUtil.loadApplicationIcon(AppUIUtil.java:184) at com.intellij.ui.AppUIUtil.loadApplicationIconImage(AppUIUtil.java:163) at com.intellij.ui.AppUIUtil.updateWindowIcon(AppUIUtil.java:82) at com.intellij.idea.StartupUtil$updateFrameClassAndWindowIconAndPreloadSystemFonts$1$3.invokeSuspend(StartupUtil.kt:577) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664) ----- Your JRE: 17.0.6+10-b829.9 amd64 (JetBrains s.r.o.) C:\IntelliJ IDEA 2023.1.2\jbr
05-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值