以问题的形式记录ImageLoader 图片加载器相关知识
问题描述:app有使用ImageLoader加载图片,假如加载某个场景图片A,后来场景换了又产生了一张图片,此时又命名为A,这时候其实图片已经换了,但是显示还是之前的图片?
分析:要解决这个问题就要了解ImageLoader的图片加载机制(参考文章)和实现原理,下面来分析一下这个国民程序员加载图片库。
从ImageLoader的disPlayImage说起
核心代码
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener, FileImageDecoder mFileImageDecoder) {
this.checkConfiguration();
........
// uri为空 展示逻辑
if(TextUtils.isEmpty(uri)) {
this.engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if(options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(this.configuration.resources));
} else {
imageAware.setImageDrawable((Drawable)null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), (Bitmap)null);
} else {
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, this.configuration.getMaxImageSize());
String memoryCacheKey;
//根据uri生成key
if(options.isMemoryCacheKeySplitQuestionMark()) {
String keyUri = StorageUtils.generateKeyUri(uri);
memoryCacheKey = MemoryCacheUtils.generateKey(TextUtils.isEmpty(keyUri)?uri:keyUri, targetSize);
} else {
memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
}
this.engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//用此key来从缓存中获取图片
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
ImageLoadingInfo imageLoadingInfo;
// 获取到了图片
if(bmp != null && !bmp.isRecycled()) {
if(this.configuration.writeLogs) {
L.d("Load image from memory cache [%s]", new Object[]{memoryCacheKey});
}
if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, defineHandler(options));
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.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(this.configuration.resources));
} else if(options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable((Drawable)null);
}
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, defineHandler(options), mFileImageDecoder);
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.engine.submit(displayTask);
}
}
}
}
}
其大致逻辑是
先缓存,在磁盘,最后没有就网络获取;这里的顺序和代码的逻辑一样的,根据图片uri生成一个key,然后用key去找图片,看看这个key是怎么生成的?
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, this.configuration.getMaxImageSize());
String memoryCacheKey;
if(options.isMemoryCacheKeySplitQuestionMark()) {
String keyUri = StorageUtils.generateKeyUri(uri);
memoryCacheKey = MemoryCacheUtils.generateKey(TextUtils.isEmpty(keyUri)?uri:keyUri, targetSize);
} else {
memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
}
public static String generateKey(String imageUri, ImageSize targetSize) {
return imageUri + "_" + targetSize.getWidth() + "x" + targetSize.getHeight();
}
关键是generateKey 方法,这里根据uri 和 内部定义的 targetSize来去生成key,这样假如图片的名称没有改变那这样得到同一个key,再回到之前displayImage方法,就会看到ImageLoader会从内存中去加载图片,这时候就不会走下面的逻辑了,导致虽然图片内容变了但是显示的图片还是之前的原因,所以这个问题到此也就分析出来了;
解决方法
很简单的,既然你是从缓存中获取图片,那我在设置Options 的时候禁用缓存就可以了。
.cacheInMemory(false).cacheOnDisk(false)
ImageLoader 同步加载图片
同步的概念理解:就是顺序执行,直到图片加载请求结束后,在去执行其他代码;可以百度 “同步网络请求什么意思” ,或者参考一下 同步和异步。
使用场景:假如推送中包含文字信息和图片信息,由于及时性的特性,要求推送产生后,先把文字推送出去(文字信息包含图片的url),并同时上传图片(这个图片上传是耗时的,等图片上传完了后url才会有图片),这样会造成两者的不同步,但app端要同时展示文字和图片?
解决办法,见代码示例:
// 由于推送文字消息和图片有时间差,当推送过来时就同步加载图片10次,若10次之后还没有加载到,则认为图片有问题
Bitmap bigBitmap = null;
for (int i = 0; i < 10; i++) {
bigBitmap = ImageLoader.getInstance().loadImageSync(largeIconUrl);
if (bigBitmap != null){
break;
}
LogUtil.debugLog("tag", "for循环: " + i);
}
if (null != bigBitmap){
图片加载出来了,并设置推送大图
builder.setLargeIcon(bigBitmap);
builder.setStyle(new Notification.BigPictureStyle().bigPicture(bigBitmap));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification = builder.build();
} else {
notification = builder.getNotification();
}
notificationManager.notify(xx, notification);
}else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification = builder.build();
} else {
notification = builder.getNotification();
}
notificationManager.notify(xx, notification);
}
由于,可以得出同步和异步的区别
- 一般的,异步中我们需要从回调接口中拿到我们需要对象,而同步方法会直接返回你所要的对象;
- 不管同步和异步其实他们都是在子线程中实现的;(但同步是的具体实现机制是什么样的呢?有待深入探讨)
- 之前一直疑惑同步请求没有用,其实也是有用的
这个大图获取是在service中实现的,自己验证了一下在actvity中直接用 loadImageSync 会报异常android.os.NetworkOnMainThreadException,所以这个后面继续研究一下?
ImageLoader 配置
参考文章 点击链接,可以看看相关配置介绍。