ImageLoader#loadImage(java.lang.String, ImageLoadingListener);
当使用这个方法加载图片的时候,如果同一本地路径下的图片被加载多次。
你会发现除了最后一个加载任务成功收到onComplete回调,前面的加载任务
都是onCanceled方法被回调。
研究了一下ImagerLoader的源码,会发现最后调用的还是displayImage方法。并且构造了一个NonViewAware对象,该对象的成员变量imageUri被赋值为你需要
加载的图片的路径。
public void loadImage(String uri, ImageLoadingListener listener) {
loadImage(uri, null, null, listener, null);
}
public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
if (targetImageSize == null) {
targetImageSize = configuration.getMaxImageSize();
}
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
// 在这里组装了一个NonViewAware对象
NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
// 接着调用了displayImage方法
displayImage(uri, imageAware, options, listener, progressListener);
}
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {
listener = defaultListener;
}
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
if (TextUtils.isEmpty(uri)) {
// 取消imageAware对象对应的加载任务
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
// 回调加载任务开始
listener.onLoadingStarted(uri, imageAware.getWrappedView());
// 从缓存中获取
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
if (options.shouldPostProcess()) {
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
} else {
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable(null);
}
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
// 提交任务到加载队列,也就是之后displayTask这个对象的run方法会被调用
engine.submit(displayTask);
}
}
}
加载任务的run方法有点长,我只把关键部分抽取出来:
LoadAndDisplayImageTask#run
@Override
public void run() {
// ....
// 省略一堆堆
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
// 继续提交一个DisplayBitmapTask任务
runTask(displayBitmapTask, syncLoading, handler, engine);
}
接着看DisplayBitmapTask的run方法:
com.nostra13.universalimageloader.core.DisplayBitmapTask#run
@Override
public void run() {
if (imageAware.isCollected()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
// isViewWasResued为true的话,cancel掉该任务
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else {
L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
displayer.display(bitmap, imageAware, loadedFrom);
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
isViewWasResued:
/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
String currentCacheKey = engine.getLoadingUriForView(imageAware);
return !memoryCacheKey.equals(currentCacheKey);
}
/** * Returns URI of image which is loading at this moment into passed {@link com.nostra13.universalimageloader.core.imageaware.ImageAware} */
String getLoadingUriForView(ImageAware imageAware) {
return cacheKeysForImageAwares.get(imageAware.getId());
}
最后调用了NonViewAware的getId方法:
@Override
public int getId() {
return TextUtils.isEmpty(imageUri) ? super.hashCode() : imageUri.hashCode();
}
而这里的getId返回的结果正是加载的图片路径,也就是说,如果同一路径下
的图片,同一个任务如果在队列中存在的话,已存在的会被cancel掉,
当被cancel掉的任务调用run方法是,直接回调onCancel方法,不会调用onComplete方法。
解决方法:
调用displayImage方法,传递一个NonViewAware对象,其iamgeUrl成员变赋值成一个递增的数字,这样的话,即使是重复路径的任务也会执行。
public void displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener) {
displayImage(uri, imageAware, null, listener, null);
}