记ImageLoader 引发的问题

以问题的形式记录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 配置

参考文章 点击链接,可以看看相关配置介绍。

猜你喜欢

转载自blog.csdn.net/sjh_389510506/article/details/85068923