ImageLoader的简单分析

刚接触android的时候看到项目里面用到了ImageLoader这个图片缓存插件,当初抱着“知其然必要知其所以然”的想法还专门下载了它的源码没头苍蝇似的毫无章法的去看了看,当然最终因为对android相关知识的了解有限而作罢。本篇也不会细致深入的对此多做说明,只是对ImageLoader的创建过程等略作梳理,方便以后的使用。
在这里,从使用的源头开始说起慢慢抽丝拨茧,ImagerLoader提供了如下的来加载展示图片的方法:
ImageLoader的简单分析_第1张图片
通常直接会使用上图中的前五个来,至于的五个接口十八ImageView先包装秤ImageAware来使用。
在使用上图中的前五个方法的时候,简单查看源码:

    public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
        displayImage(uri, new ImageViewAware(imageView), options, null, null);
    }

就是把imageView封装成ImageViewAware后传给了如下方法:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) 

所以直接分析上面的方法就可以!
进入displayImage这个参数最多的方法,其实上图中的其他重载方法都会调用这个方法,该方法首先会执行checkConfiguration();方法来检测configuration是否为null,如果为null就抛出异常。

private void checkConfiguration() {
        if (configuration == null) {
            throw new IllegalStateException(ERROR_NOT_INIT);
        }
    }

ImageLoaderConfiguration的创建

那么这个configuration是什么呢?其实就是ImageLoaderConfiguration,其实用过ImageLoader的都应该清楚,我们在使用ImageLoader的时候首先就要在某一个地方比如Application的onCreate方法里面配置ImageLoaderConfiguration,用来初始化ImageLoader的一些配置参数:诸如缓存策略、可以缓存的文件数量等。
简单的配置代码如下:

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(mContext)
                .threadPoolSize(Thread.NORM_PRIORITY - 2) 
                .threadPriority(Thread.NORM_PRIORITY - 2) 
                .memoryCache(new WeakMemoryCache())
                .memoryCacheSize(2 * 1024 * 1024)
                .memoryCacheSizePercentage(13) // default
                .diskCache(new UnlimitedDiscCache(cacheDir)
                .build(); // 开始构建
ImageLoader.getInstance().init(config);

这明显是一个Builder模式的应用,Builder模式的好处之一就是可以对比较复杂的对象逐步创建一个个小组件,然后由这些小组件最终完成复杂对象的初始化(构建)。上面代码通过Builder这个类来一步步创建ImageLoaderConfiguration这个类所需的一些参数之后,最终会通过build()方法来完成ImageLoaderConfiguation对象的创建!至于构建模式的说明读者可以参考网上的一些介绍。
其实build()方面也很简单:

public ImageLoaderConfiguration build() {
            initEmptyFieldsWithDefaultValues();
            //构造方法里面的this,指的就是Builder对象
            return new ImageLoaderConfiguration(this);
        }

build方法做了两个工作:
1)通过initEmptyFieldsWithDefaultValues()方法来设置一些默认的参数(前提是如果你通过Builder构建ImageLoaderConfiguation这个对象的过程中没有配置ImageLoaderConfiguation所需的参数),比如在Build的过程中没有调用Builder的imageDownloader方法来配置自己的下载图片的逻辑,那么在initEmptyFieldsWithDefaultValues方法中就会采用ImageLoader默认的下载方式来进行图片的下载:

    if (downloader == null) {
                downloader = DefaultConfigurationFactory.createImageDownloader(context);
            }

2)返回ImageLoaderConfiguration对象。
ImageLoaderConfiguration的构造参数也很简单,就是把Builder对象构建的一个个组件设置给ImageLoaderConfiguration!该类的构造器是private的,切ImageLoaderConfiguration类是final的,而且它所持有的所有属性也是final的,一旦build完毕就不可更改!

private ImageLoaderConfiguration(final Builder builder) {
        resources = builder.context.getResources();
        maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
        maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
        maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
        maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
        processorForDiskCache = builder.processorForDiskCache;
        taskExecutor = builder.taskExecutor;
        taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
        threadPoolSize = builder.threadPoolSize;
        threadPriority = builder.threadPriority;
        tasksProcessingType = builder.tasksProcessingType;
        diskCache = builder.diskCache;
        memoryCache = builder.memoryCache;
        defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
        downloader = builder.downloader;
        decoder = builder.decoder;

        customExecutor = builder.customExecutor;
        customExecutorForCachedImages = builder.customExecutorForCachedImages;

        networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);
        slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);

    }

DisplayImageOptions的创建和使用

上面主要讲了checkConfiguration()以及ImageLoaderConfiguation的构建过程。下面接着对displayImage方法的说明。接着就是我们非常熟悉的DisplayImageOptions的使用了,如果用户自己在使用displayImage一系列重载方法的时候没有传自定义的DisplayImageOptions对象,那么就会使用默认的DisplayImageOptions对象:

if (options == null) {
  //configuration为上文所说的ImageLoaderConfiguration对象。
            options = configuration.defaultDisplayImageOptions;
        }

`这个默认的DisplayImageOptions是又Builder创建的ImageLoaderConfiguration来初始化的,代码如下:

final DisplayImageOptions defaultDisplayImageOptions;
private ImageLoaderConfiguration(final Builder builder) {
       //此处省略了若干代码
    defaultDisplayImageOptions = builder.defaultDisplayImageOptions;

正如上面代码所示DisplayImageOptions类里面的defaultDisplayImageOptions引用是由builder构建过来的!你可以通过Builder类的defaultDisplayImageOptions方法来完成对defaultDisplayImageOptions的初始化工作:

public Builder defaultDisplayImageOptions(DisplayImageOptions defaultDisplayImageOptions) {
            this.defaultDisplayImageOptions = defaultDisplayImageOptions;
            return this;
        }

那么问题来了?如果在创建Builder的时候如果没有调用defaultDisplayImageOptions()方法设置options岂不是为null?还记得上文build()方法里面的调用的initEmptyFieldsWithDefaultValues()么?这个方法就是:防止在使用Builder创建ImageLoaderConfiguration的时候客户端没有配置一些ImageLoader需要的组件而默认用ImageLoader一些组件。比如如果你的DisplayImageOptions没有在客户端指定,那么在initEmptyFieldsWithDefaultValues()里面有如下代码确保了defaultDisplayImageOptions不为null:

if (defaultDisplayImageOptions == null) {

                defaultDisplayImageOptions = DisplayImageOptions.createSimple();
            }
    public static DisplayImageOptions createSimple() {
        return new Builder().build();
    }

其实上面的代码createSimple也是Builder模式的应用。到此ImageLoaderConfiguration的创建才算最终完成了,那么如何把这个对象叫给ImageLoader来使用呢?

ImageLoader的工作细节梳理

ImageLoader是一个单例类,提供了如下方式来完成了ImageLoader的初始化工作:

Imageloader.getInstance().init(ImageLoaderConfiguration)

既然是通过init方法来完成ImageLoader与ImageLoaderCOnfiguration的关联,那么就让我们看看init方法都做了些神马?

public synchronized void init(ImageLoaderConfiguration configuration) {
        if (configuration == null) {
            throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
        }
        if (this.configuration == null) {
            engine = new ImageLoaderEngine(configuration);
            this.configuration = configuration;
        } else {

        }
    }

也很简单,就是把configuration简单的赋予了ImageLoader.configuration引用!到此处ImageLoader初始化也完毕了,同样ImageLoaderConfiguration也初始化完毕了!所以就可以放心的调用ImageLoader.getInstance().displayImage来完成工作了!
在这里说一个可能不是技巧的技巧:在用DisplayImageOptions来控制页面显示样式的时候如果你的应用里面的图片样式:比如失败的图片,加载过程中的图片等都许多相似的地方,那么你就可以通过Builder的defaultDisplayImageOptions方法来手动指定默认的Options,个别页面如果需要不同的风格,就可以调用displayImage的其他重载方法传入具体通过DisplayImageOptions的Builder构建Options的对象就可以了,简单的配置代码如下:

private void initImageLoader(Context context) {
        DisplayImageOptions options = new     DisplayImageOptions.Builder().build();
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).
        defaultDisplayImageOptions(options)//把上面创建的options交给ImageLoaderConfiguration
        .build();
    ImageLoader.getInstance().init(config);
    }

ImageLodaer对URL为null的处理

在前面的一系列说明之后,终于进入的Imageloader的核心工作:展示图片!

  • 如果图片资源地址为null的情况:处理逻辑很简单,代码如下:
if (TextUtils.isEmpty(uri)) {
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingStarted(uri, imageAware.getWrappedView());
            if (options.shouldShowImageForEmptyUri()) {
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {//没有指定emptyUri的情况
                imageAware.setImageDrawable(null);
            }
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
            return;
        }

如果在配置DisplayImageOptions的时候通过它的Builder调用了showImageForEmptyUri方法,那么就让ImageView显示showImageForEmptyUri指定的那个图片资源,否则就神马都不显示 如果阅读仔细的话,上面也提供了加载图片的监听listener.onLoadingStarted,listener.onLoadingComplete,你也可以调用displayImage相关重载方法传入自己的ImageLoadingListener对象,否则ImageLoader会调用自己的默认实现。
图片资源非null的情况,那就很简单了,就是使用缓存的思路了:先从缓存中读取,如果缓存存在就用缓存中的图片资源,否则就下载新的资源并进行缓存!代码如下:

//1.对缓存进行一些配置
         ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
        //2.开始加载图片监听
        listener.onLoadingStarted(uri, imageAware.getWrappedView());
        //3.从内存缓存中获取Bitmap
         Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp != null && !bmp.isRecycled()) {//缓存存在
            if (options.shouldPostProcess()) {//如果对DisplayImageOptions设置了BitmapProcessor

                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
                //开启一个Runnable
                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
                if (options.isSyncLoading()) {//同步执行显示图片逻辑
                    displayTask.run();
                } else {//异步执行显示图片逻辑
                    engine.submit(displayTask);
                }
            } else {//如果没有配置BitmapProcessor
                //通过BitmapDisplayer来完成显示图片的逻辑,该Displayer如果没指定的话就是用DefaultConfigurationFactory.createBitmapDisplayer();
                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));
            //初始化一个下载的Runnable
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));
            if (options.isSyncLoading()) {//同步处理下载
                displayTask.run();
            } else {//异步下载图片
                engine.submit(displayTask);
            }
        }

上面代码中的注释说明了各个模块的作用,关于详细的内容限于篇幅以及时间太晚就另外开一篇博客进行说明。
总结一下本博客的要点:
1)ImageLoader的使用先通过Builder模式构建ImageLoaderConfiguration对象
2)通过Builder模式一步步创建DisplayImageOptions对象。
3)通过ImageLoader的init方法把ImageLoaderConfiguration对象和ImageLoader对象关联起来,当然也使得ImageLoader也关联了DisplayImageOptions对象
4)通过ImageLoader的displayImage重载方法,结合ImageLoaderConfiguration对象和DisplayImageOptions对象完成了对ImageView的展示图片的功能。
简单的用图片表示其三者之间的关系:
ImageLoader的简单分析_第2张图片
图片画的有点丑,不过也凑合,本篇博客到此为止,如有不当之处欢迎批评指正共同学习和提高。

你可能感兴趣的:(ImageLoader的简单分析)