千家信息网

62. ImageLoader源代码-流程分析

发表于:2025-02-01 作者:千家信息网编辑
千家信息网最后更新 2025年02月01日,一. ImageLoader简介Android library #1 on GitHub. UIL aims to provide a powerful, flexible and highly cu
千家信息网最后更新 2025年02月01日62. ImageLoader源代码-流程分析

一. ImageLoader简介

Android library #1 on GitHub. UIL aims to provide a powerful, flexible and highly customizable instrument for image loading, caching and displaying. It provides a lot of configuration options and good control over the image loading and caching process.

根据Github上面的注释,ImageLoader是一个强大的,灵活的,高度可定制化的图片加载,缓存已经展示的框架。它提供了大量的可配置选项,可以很好的控制图片的加载和缓存进度。

1.1 Github源代码地址

https://github.com/nostra13/Android-Universal-Image-Loader

1.2 主要类及其功能介绍

类名意义
ImageLoaderImageLoader的主要操作入口类,比如初始化,请求加载图片。
ImageLoaderEngineImageLoader的发动机,包含几个Executor线程池,可以执行各种任务,有些Executor可以在configuration中配置。
ImageViewAware传入图片控件ImageView的包装,对ImageView弱引用(防止内存泄漏),封装了一些方法,可以更加方便的操作ImageView,比如获取宽高,设置图片显示等。
DisplayImageOptions请求显示图片时的参数,比如默认图片,失败图片,是否使用内存缓存等等
ImageLoadingListener图片加载的监听器,比如onLoadingStarted,onLoadingComplete
MemoryCacheMemoryCache是图片内存缓存的一个接口,包括多种实现机制,比如Lru, FIFO, LargestLimited等
DiskCache图片磁盘缓存接口,包括多种缓存命名算法,比如md5,hashcode等
ImageLoadingInfo内存中没有找到图片,准备去其他地方找图片的时候,为了便于操作封装的对象,比如图片uri,memorykey, imageLoadinglistener,progressListener, loadFromUriLock
LoadedFrom枚举类型,表明图片从哪里获取,包括3种类型 NETWORK(网络), DISC_CACHE(磁盘,sd卡), MEMORY_CACHE(内存)
ImageLoaderConfiguration非常重要的对象,在Application中初始化,包含了MemoryCache,DiskCache,ImageDownloader,ImageDecoder等
ImageDownloader图片下载接口,有些实现子类,比如BaseImageDownloader,SlowNetworkImageDownloader,NetworkDeniedImageDownloader
BaseImageDownloader基本的图片下载类,支持网络,assets, content, drawable等图片获取
SlowNetworkImageDownloader底网速下图片获取
BitmapDisplayer图片显示抽象类,包括各种图片显示效果,比如最普通的显示图片,圆角图片显示等

1.3 代码包及其含义

包名作用
com.nostra13.universalimageloader.cache.disc磁盘缓存命名和存储的算法实现,比如md5和hashcode名称,限制使用时间存储等
com.nostra13.universalimageloader.cache.memory内存缓存算法的实现类,包括先进先出,Lru等算法
com.nostra13.universalimageloader.coreImageLoader的核心代码和主要工作流程类,比如ImageLoader,ImageLoaderConfiguration,ImageLoaderEngine等。
com.nostra13.universalimageloader.core.assist辅助类,
com.nostra13.universalimageloader.core.decode解码,比如从磁盘文件解码成Bitmap
com.nostra13.universalimageloader.core.display图片显示效果类,比如圆角,淡入效果等
com.nostra13.universalimageloader.core.download图片下载类,支持网络下载图片,文件读取图片,assets图片,drawable,已经contentProvider读取图片
com.nostra13.universalimageloader.core.imageawareImageView的封装,提供了对ImageView的便捷操作,比如获取ImageView高度宽度,是否被回收等
com.nostra13.universalimageloader.core.listener监听器,包括图片加载监听,加载进度监听,列表滑动监听
com.nostra13.universalimageloader.core.process外放给调用者处理图片的能力,获取到图片之后,在显示之前,调用者可以设置此监听器,处理图片,比如切割图片。
com.nostra13.universalimageloader.utils工具类
  1. 4 图片加载序列图

二. 简单使用

https://www.cnblogs.com/yimi-yangguang/p/5715350.html

2.1 Application

public class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)                .memoryCacheExtraOptions(480, 800) // default = device screen ,默认为屏幕宽高 dimensions,内存缓存的最大宽高                .diskCacheExtraOptions(480, 800, null)//磁盘缓存最大宽高,默认不限制                .threadPriority(Thread.NORM_PRIORITY - 2) // default //线程优先级                .denyCacheImageMultipleSizesInMemory() //阻止内存中多尺寸缓存                .memoryCacheSize(2 * 1024 * 1024) //配置缓存大小                .memoryCacheSizePercentage(13) // default //缓存百分比                .diskCacheSize(50 * 1024 * 1024) //磁盘缓存大小,只在使用默认缓存有效                .diskCacheFileCount(100)  //磁盘缓存文件数,只在使用默认缓存有效                .writeDebugLogs() //打印调试日志                .build();       ImageLoader.getInstance().init(config);//初始化            }}

2.2 加载图片

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ImageView imageView = findViewById(R.id.img_test);        ImageLoader imageLoader = ImageLoader.getInstance();        DisplayImageOptions options = new DisplayImageOptions.Builder()                .showImageOnLoading(R.drawable.ic_launcher_background) // resource or drawable                .showImageForEmptyUri(R.drawable.ic_launcher_background) // resource or drawable                .showImageOnFail(R.drawable.ic_launcher_background) // resource or drawable                .resetViewBeforeLoading(false)  // default                .delayBeforeLoading(1000)                .postProcessor(new BitmapProcessor() {                    @Override                    public Bitmap process(Bitmap bitmap) {                        Log.d("sandy", "process bitmap...");                        return bitmap;                    }                })                .showImageOnLoading(R.drawable.ic_launcher_foreground)                .cacheInMemory(false) // default                .cacheOnDisk(false) // default                .considerExifParams(false) // default                .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default                .bitmapConfig(Bitmap.Config.ARGB_8888) // default                .build();        imageLoader.displayImage("http://img3.imgtn.bdimg.com/it/u=2200166214,500725521&fm=27&gp=0.jpg",                imageView, options, new ImageLoadingListener() {                    @Override                    public void onLoadingStarted(String imageUri, View view) {                        Log.d("sandy", "onLoadingStarted imageUri: " + imageUri);                    }                    @Override                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {                        Log.d("sandy", "onLoadingFailed imageUri: " + imageUri                        + " failReason: " + failReason);                    }                    @Override                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {                        Log.d("sandy", "onLoadingComplete imageUri: " + imageUri);                    }                    @Override                    public void onLoadingCancelled(String imageUri, View view) {                        Log.d("sandy", "onLoadingCancelled imageUri: " + imageUri);                    }                }, new ImageLoadingProgressListener(){                    @Override                    public void onProgressUpdate(String imageUri, View view, int current, int total) {                        Log.d("sandy", "onProgressUpdate current: " + current + " total: " + total);                    }                });    }}

三. 流程分析-初始化

按照上面的使用方法,进行ImageLoader的源代码分析,首先看Application的onCreate里面ImageLoader的代码。

3.1 初始化ImageLoaderConfiguration

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)                .memoryCacheExtraOptions(480, 800) // default = device screen ,默认为屏幕宽高 dimensions,内存缓存的最大宽高                .diskCacheExtraOptions(480, 800, null)//磁盘缓存最大宽高,默认不限制                .threadPriority(Thread.NORM_PRIORITY - 2) // default //线程优先级                .denyCacheImageMultipleSizesInMemory() //阻止内存中多尺寸缓存                .memoryCacheSize(2 * 1024 * 1024) //配置缓存大小                .memoryCacheSizePercentage(13) // default //缓存百分比                .diskCacheSize(50 * 1024 * 1024) //磁盘缓存大小,只在使用默认缓存有效                .diskCacheFileCount(100)  //磁盘缓存文件数,只在使用默认缓存有效                .writeDebugLogs() //打印调试日志                .build();

这是一个典型的构造者模式,构造者模式一般适用于属性比较多的场景。

在设置完各种属性后,最后来看看build方法。

3.1.1 build

/** Builds configured {@link ImageLoaderConfiguration} object */public ImageLoaderConfiguration build() {   initEmptyFieldsWithDefaultValues();   return new ImageLoaderConfiguration(this);}

首先会调用initEmptyFieldsWithDefaultValues,如果用户没有设置一些属性,那么就会为他们初始化默认值。

private void initEmptyFieldsWithDefaultValues() {            if (taskExecutor == null) {                taskExecutor = DefaultConfigurationFactory                        .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);            } else {                customExecutor = true;            }            if (taskExecutorForCachedImages == null) {                taskExecutorForCachedImages = DefaultConfigurationFactory                        .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);            } else {                customExecutorForCachedImages = true;            }            if (diskCache == null) {                if (diskCacheFileNameGenerator == null) {                    diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();                }                diskCache = DefaultConfigurationFactory                        .createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);            }            if (memoryCache == null) {                memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);            }            if (denyCacheImageMultipleSizesInMemory) {                memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());            }            if (downloader == null) {                downloader = DefaultConfigurationFactory.createImageDownloader(context);            }            if (decoder == null) {                decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs);            }            if (defaultDisplayImageOptions == null) {                defaultDisplayImageOptions = DisplayImageOptions.createSimple();            }        }

最后build方法可以产生出一个ImageLoaderConfiguration对象

return new ImageLoaderConfiguration(this);

得到ImageLoaderConfiguration这个配置对象后,接下来就会利用它来初始化ImageLoader

ImageLoader.getInstance().init(config);//初始化 

继续往下分析,首先看ImageLoader.getInstance()

3.2 ImageLoader.getInstance()

ImageLoader imageLoader = ImageLoader.getInstance();

继续看ImageLoader.getInstance()方法

3.2.1 ImageLoader.getInstance

/** Returns singleton class instance */    public static ImageLoader getInstance() {        if (instance == null) {            synchronized (ImageLoader.class) {                if (instance == null) {                    instance = new ImageLoader();                }            }        }        return instance;    }

getInstance可以看出是一个单例模式,而且是做了效率优化(两层if判断,第一层可以过滤大部分访问,从而减少进入synchronized锁的次数)。

3.3 ImageLoader.init(config)

/**     * Initializes ImageLoader instance with configuration.
* If configurations was set before ( {@link #isInited()} == true) then this method does nothing.
* To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first. * * @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration} * @throws IllegalArgumentException if configuration parameter is null */ public synchronized void init(ImageLoaderConfiguration configuration) { if (configuration == null) { throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL); } if (this.configuration == null) { L.d(LOG_INIT_CONFIG); engine = new ImageLoaderEngine(configuration); this.configuration = configuration; } else { L.w(WARNING_RE_INIT_CONFIG); } }

根据注释,我们利用传入的configuration对象初始化ImageLoader,如果这个configuration之前已经被设置过(isInit=true),那么就不会发生什么。

如果想用现在的configuration替换之前的configuration对象,那么需要先调用ImageLoader.destory()方法进行销毁。

如果一些正常的话,就出产生一个ImageLoaderEngine对象,

3.3.1 ImageLoaderEngine

private Executor taskExecutor;private Executor taskExecutorForCachedImages;private Executor taskDistributor;ImageLoaderEngine(ImageLoaderConfiguration configuration) {        this.configuration = configuration;        taskExecutor = configuration.taskExecutor;        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();    }

ImageLoaderEngine把传入的configuration保存起来,然后包含了几个Executor,用来执行各种异步任务,所以叫做Engine,发动机。

其中taskExecutor和taskExecutorForCachedImages是从configuration里面传进来的,那换句话就是说,是可以我们在configuration中配置的,然后自己也创建了一个taskDistributor 这个Executor。

这样Application里面初始化流程久分析完成了,接下来看Activity里面怎么使用ImageLoader

四. 流程分析-加载图片

先继续贴一段请求加载图片的代码,在Activity的onCreate里面。

首先

  1. 首先设置布局文件
  2. findViewById找到想要展示图片的ImageView控件
  3. 初始化展示的配置,当然也可以不用,ImageLoader可以使用默认的。
  4. 调用imageLoader.displayImage方法请求加载图片,其中可以有图片地址,图片控件,展示配置参数,加载图片的监听器,加载进度监听器。
protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            setContentView(R.layout.activity_main);            ImageView imageView = findViewById(R.id.img_test);            ImageLoader imageLoader = ImageLoader.getInstance();            DisplayImageOptions options = new DisplayImageOptions.Builder()                    .showImageOnLoading(R.drawable.ic_launcher_background) // resource or drawable                    .showImageForEmptyUri(R.drawable.ic_launcher_background) // resource or drawable                    .showImageOnFail(R.drawable.ic_launcher_background) // resource or drawable                    .resetViewBeforeLoading(false)  // default                    .delayBeforeLoading(1000)                    .postProcessor(new BitmapProcessor() {                        @Override                        public Bitmap process(Bitmap bitmap) {                            Log.d("sandy", "process bitmap...");                            return bitmap;                        }                    })                    .showImageOnLoading(R.drawable.ic_launcher_foreground)    //                .displayer(new RoundedBitmapDisplayer(5))                    .cacheInMemory(false) // default                    .cacheOnDisk(false) // default                    .considerExifParams(false) // default                    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default                    .bitmapConfig(Bitmap.Config.ARGB_8888) // default                    .build();            imageLoader.displayImage("http://img3.imgtn.bdimg.com/it/u=2200166214,500725521&fm=27&gp=0.jpg",                    imageView, options, new ImageLoadingListener() {                        @Override                        public void onLoadingStarted(String imageUri, View view) {                            Log.d("sandy", "onLoadingStarted imageUri: " + imageUri);                        }                        @Override                        public void onLoadingFailed(String imageUri, View view, FailReason failReason) {                            Log.d("sandy", "onLoadingFailed imageUri: " + imageUri                            + " failReason: " + failReason);                        }                        @Override                        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {                            Log.d("sandy", "onLoadingComplete imageUri: " + imageUri);                        }                        @Override                        public void onLoadingCancelled(String imageUri, View view) {                            Log.d("sandy", "onLoadingCancelled imageUri: " + imageUri);                        }                    }, new ImageLoadingProgressListener(){                        @Override                        public void onProgressUpdate(String imageUri, View view, int current, int total) {                            Log.d("sandy", "onProgressUpdate current: " + current + " total: " + total);                        }                    });        }

DisplayImageOptions图片展示参数,你可以不指定,也可以指定,表示一些展示的参数,比如默认图片(在网络图片还没有加载出来之前显示),加载失败图片,是否从内存加载,这些后面再分析,不涉及流程的分析。

所以继续看ImageLoader.displayImage(xxx)方法

4.1 ImageLoader.displayImage

public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {        displayImage(uri, new ImageViewAware(imageView), options, listener, progressListener);    }public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {        displayImage(uri, imageAware, options, null, 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)) {            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 {                engine.submit(displayTask);            }        }    }

4.2 ImageViewAware

在displayImage的时候,会使用ImageView对象,初始化一个ImageViewAware对象。

new ImageViewAware(imageView)

ImageViewAware的继承关系如下:

里面主要是做了一个View的弱引用,可以访问传入的ImageView的一些属性,比如高度宽度,设置显示图片等等。

protected Reference viewRef;

之所以搞出一个ImageViewAware,是因为ImageLoader想方便操作传入的ImageView对象。

下面来看displayImage(xx)里面具体的内容

4.3 displayImage条件判断

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;   }   ...}private void checkConfiguration() {   if (configuration == null) {      throw new IllegalStateException(ERROR_NOT_INIT);   }}

首先会检查configuration,checkConfiguration,如果configuration==null,那么就会报错。也就是说如果没有调用之前我们说的ImageLoader.init(),初始化如下.

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

然后imageAware是否null,如果null,那么就报错

接着检查,listener, options是否为null,如果是null,那么设置为default值。

继续往下面看代码

4.4 加载空图片地址

如果传入的图片地址是null,那么将走下面的的分支

if (TextUtils.isEmpty(uri)) {   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;}

上面这段代码做了下面几件事

  1. 清空engine里面取消对这个ImageAware的缓存
  2. 调用listener.onLoadingStarted,如果你传入了listener,那么这个时候就会回调。
  3. 判断options里面是否有为空时候的图片,如果有,那么就把ImageView设置这张图片
  4. 如果没有设置为空时候的图片,那么ImageView设置图片为null.
  5. 然后调用listener.onLoadingComplete方法

4.5 初始化ImageSize

if (targetSize == null) {            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());        }public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {        int width = imageAware.getWidth();        if (width <= 0) width = maxImageSize.getWidth();        int height = imageAware.getHeight();        if (height <= 0) height = maxImageSize.getHeight();        return new ImageSize(width, height);    }

根据传入的ImageView获取高度和宽度,如果没有宽度和高度,就用最大的宽度和高度。

4.6 获取Image Memory key

private static final String URI_AND_SIZE_SEPARATOR = "_";private static final String WIDTH_AND_HEIGHT_SEPARATOR = "x";String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);public static String generateKey(String imageUri, ImageSize targetSize) {        return new StringBuilder(imageUri).append(URI_AND_SIZE_SEPARATOR).append(targetSize.getWidth()).append(WIDTH_AND_HEIGHT_SEPARATOR).append(targetSize.getHeight()).toString();    }

产生Image Key的方式是: 图片URL_图片宽度x图片高度

4.7 缓存ImageAware和Image Memory Key

engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {        cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);    }

4.8 通知Listener回调onLoadingStarted

listener.onLoadingStarted(uri, imageAware.getWrappedView());

4.9 根据Memory Key从缓存里面获取图片

Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);final MemoryCache memoryCache;public interface MemoryCache {    /**     * Puts value into cache by key     *     * @return true - if value was put into cache successfully, false - if value was not put into     * cache     */    boolean put(String key, Bitmap value);    /** Returns value by key. If there is no value for key then null will be returned. */    Bitmap get(String key);    /** Removes item by key */    Bitmap remove(String key);    /** Returns all keys of cache */    Collection keys();    /** Remove all items from cache */    void clear();}

MemoryCache是图片内存缓存的一个接口,包括多种实现机制,比如Lru, FIFO, LargestLimited等,如下图:

具体缓存算法可以后续分析

4.10 从内存中获取到图片

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);            }        } 

如果从内存里面获取到了图片,那么就准备显示,又分成两种情况,看业务需不需要重新处理图片,如果图片显示选项设置了shouldPostProcess,就像

DisplayImageOptions options = new DisplayImageOptions.Builder()                .postProcessor(new BitmapProcessor() {                    @Override                    public Bitmap process(Bitmap bitmap) {                        Log.d("sandy", "process bitmap...");                        return bitmap;                    }                })

那么就产生一个ProcessAndDisplayImageTask

final class ProcessAndDisplayImageTask implements Runnable {    ...    @Override    public void run() {        L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);        //获取图片显示选项中的processor对象        BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();        //回调processor.process来处理图片        Bitmap processedBitmap = processor.process(bitmap);        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,                LoadedFrom.MEMORY_CACHE);        LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);    }}   

在ProcessAndDisplayImageTask里面首先获取到BitmapProcessor

BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();

然后回调processor.process方法()

Bitmap processedBitmap = processor.process(bitmap);

然后新建一个DisplayBitmapTask对象,用来显示图片和回调listener的回调方法,如下:

final class DisplayBitmapTask implements Runnable {    @Override    public void run() {        if (imageAware.isCollected()) {            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());        } else if (isViewWasReused()) {            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);        }    }}

那如果使用者不需要自己另外处理图片,那么就直接显示好了。

if (options.shouldPostProcess()) {    ...} else {    options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);    listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);}

4.11 内存中没有图片

如果内存中没有获取到图片,比如第一次加载图片,那该怎么办呢?

if (bmp != null && !bmp.isRecycled()) {            ...        } 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 {                engine.submit(displayTask);            }        }
  1. 先判断是否需要在加载图片的过程中,显示loading图片,如果是,那么先显示loading图片。
  2. 如果需要在加载前先清空图片,那么就把ImageView显示图片设置成null。
  3. 然后封装出一个ImageLoadingInfo对象,因为要操作的属性是在有点多。
  4. 然后封装出一个LoadAndDisplayImageTask对象,接下来所有的逻辑就封装在LoadAndDisplayImageTask对象里面,我们重点看LoadAndDisplayImageTask的实现。

4.12 LoadAndDisplayImageTask加载和显示图片

final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {    @Override    public void run() {        if (waitIfPaused()) return;        if (delayIfNeed()) return;        ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;        L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);        if (loadFromUriLock.isLocked()) {            L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);        }        loadFromUriLock.lock();        Bitmap bmp;        try {            checkTaskNotActual();            bmp = configuration.memoryCache.get(memoryCacheKey);            if (bmp == null || bmp.isRecycled()) {                bmp = tryLoadBitmap();                if (bmp == null) return; // listener callback already was fired                checkTaskNotActual();                checkTaskInterrupted();                if (options.shouldPreProcess()) {                    L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);                    bmp = options.getPreProcessor().process(bmp);                    if (bmp == null) {                        L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);                    }                }                if (bmp != null && options.isCacheInMemory()) {                    L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);                    configuration.memoryCache.put(memoryCacheKey, bmp);                }            } else {                loadedFrom = LoadedFrom.MEMORY_CACHE;                L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);            }            if (bmp != null && options.shouldPostProcess()) {                L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);                bmp = options.getPostProcessor().process(bmp);                if (bmp == null) {                    L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);                }            }            checkTaskNotActual();            checkTaskInterrupted();        } catch (TaskCancelledException e) {            fireCancelEvent();            return;        } finally {            loadFromUriLock.unlock();        }        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);        runTask(displayBitmapTask, syncLoading, handler, engine);    }}

4.12.1 首先判断waitIfPaused,engine是否在暂停状态,如果是,那么就直接return了。

if (waitIfPaused()) return;

4.12.2 然后根据图片显示参数里面判断这次请求是否需要延迟,如果是,那么就延迟指定的时间。

if (delayIfNeed()) return;private boolean delayIfNeed() {   if (options.shouldDelayBeforeLoading()) {      L.d(LOG_DELAY_BEFORE_LOADING, options.getDelayBeforeLoading(), memoryCacheKey);      try {         Thread.sleep(options.getDelayBeforeLoading());      } catch (InterruptedException e) {         L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);         return true;      }      return isTaskNotActual();   }   return false;}

4.12.3 根据Uri进行锁操作

loadFromUriLock.lock();

loadFromUriLock是ImageLoader里面和uri关联的,所以这里锁的话意味着同一个uri,一个时间只能有一个请求。

ReentrantLock getLockForUri(String uri) {        ReentrantLock lock = uriLocks.get(uri);        if (lock == null) {            lock = new ReentrantLock();            uriLocks.put(uri, lock);        }        return lock;    }

4.12.4 检查ImageView是否被回收

try {    checkTaskNotActual();    ...} catch (TaskCancelledException e) {    fireCancelEvent();    return;} finally {    loadFromUriLock.unlock();}private void checkTaskNotActual() throws TaskCancelledException {   checkViewCollected();   checkViewReused();}

如果ImageView已经被回收,那就没必要去请求图片加载了,直接抛异常,然后catch住,结束task任务

private void checkViewCollected() throws TaskCancelledException {   if (isViewCollected()) {      throw new TaskCancelledException();//会被上面的catch捕获   }}

4.12.5 再次从内存里面读取图片

bmp = configuration.memoryCache.get(memoryCacheKey);

那为什么需要再次从内存里面读取图片呢,还记得上面这个锁吗?如果是同一个url,发起两个请求,那么就会锁住一个,第二个请求形成等待,在等待完成后,那么正常的话就会从内存里面读取到图片,不要从网络上再次请求。

4.12.6 从内存中成功获取到图片

if (bmp == null || bmp.isRecycled()) {   ...} else {   loadedFrom = LoadedFrom.MEMORY_CACHE;   L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);}

我们先不分析if分支,if分支是从内存中没有获取到图片,先看else

else里面没有做什么事情,只是简单的打印了一下日志(经过等待从内存中获取图片成功),然后把获取的类型设置成LoadedFrom.MEMORY_CACHE。

LoadedFrom包括3种类型,网络,磁盘,内存。可以参考文章最开始的概念介绍。

4.12.7 如果从内存中没有获取图片

如果从内存里面没有获取到图片,那就走if分支,也就是bmp == null || bmp.isRecycled()

if (bmp == null || bmp.isRecycled()) {   bmp = tryLoadBitmap();   if (bmp == null) return; // listener callback already was fired   checkTaskNotActual();   checkTaskInterrupted();   if (options.shouldPreProcess()) {      L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);      bmp = options.getPreProcessor().process(bmp);      if (bmp == null) {         L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);      }   }   if (bmp != null && options.isCacheInMemory()) {      L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);      configuration.memoryCache.put(memoryCacheKey, bmp);   }} else {   ...}

首先就会看到 bmp = tryLoadBitmap(); 尝试获取Bitmap,这个方法包含的东西很多,我们可以待会再讲。我们先看if分支后面的逻辑。

如果没有获取到bmp,比如指定的Uri是错误,那么就直接返回。

接下来继续判断ImageView是否被回收以及线程是否被中断,如果都通过之后,那么就判断是否需要处理图片,如果需要,那么就回调processor.process(bmp)来处理图片。

bmp = options.getPreProcessor().process(bmp);

最后,判断bmp != null以及是否需要存入内存(调用的地方可以设置是否需要存入内存),如果需要的话(一般都需要),那么就会存入内存缓存。

存入内存缓存后,那么下次就可以从内存中获取了图片了。

那接下来分析tryLoadBitmap()

4.12.8 tryLoadBitmap

先来大概说下思路,首先会去磁盘上获取图片,如果没有则从网络获取,获取完毕后,会存入磁盘,下面来看代码。

private Bitmap tryLoadBitmap() throws TaskCancelledException {   Bitmap bitmap = null;   try {      File imageFile = configuration.diskCache.get(uri);      if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {         L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);         loadedFrom = LoadedFrom.DISC_CACHE;         checkTaskNotActual();         bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));      }      if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {         L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);         loadedFrom = LoadedFrom.NETWORK;         String imageUriForDecoding = uri;         if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {            imageFile = configuration.diskCache.get(uri);            if (imageFile != null) {               imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());            }         }         checkTaskNotActual();         bitmap = decodeImage(imageUriForDecoding);         if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {            fireFailEvent(FailType.DECODING_ERROR, null);         }      }   } catch (IllegalStateException e) {      fireFailEvent(FailType.NETWORK_DENIED, null);   } catch (TaskCancelledException e) {      throw e;   } catch (IOException e) {      L.e(e);      fireFailEvent(FailType.IO_ERROR, e);   } catch (OutOfMemoryError e) {      L.e(e);      fireFailEvent(FailType.OUT_OF_MEMORY, e);   } catch (Throwable e) {      L.e(e);      fireFailEvent(FailType.UNKNOWN, e);   }   return bitmap;}

首先是尝试从磁盘获取

File imageFile = configuration.diskCache.get(uri);

如果获取到了,那么读取这个文件,读出Bitmap,同时把类型标记成从磁盘读取。

if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {   L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);   loadedFrom = LoadedFrom.DISC_CACHE;   checkTaskNotActual();   bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));}

如果从磁盘上面获取是失败,获取磁盘上面没有这个文件,那么bitmap == null

于是,就会从网络上尝试获取图片,如下:

if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {   L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);   loadedFrom = LoadedFrom.NETWORK;   String imageUriForDecoding = uri;   if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {      imageFile = configuration.diskCache.get(uri);      if (imageFile != null) {         imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());      }   }   checkTaskNotActual();   bitmap = decodeImage(imageUriForDecoding);   if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {      fireFailEvent(FailType.DECODING_ERROR, null);   }}
  1. 先把类型设置成网络获取 loadedFrom = LoadedFrom.NETWORK;
  2. 然后尝试从网络获取图片并缓存图片到磁盘上-tryCacheImageOnDisk,对于这个方法的名称持保守态度,怪怪的。
  3. 然后解析出bitmap
    bitmap = decodeImage(imageUriForDecoding);

4.12.9 tryCacheImageOnDisk下载图片并存入磁盘

/** @return true - if image was downloaded successfully; false - otherwise */private boolean tryCacheImageOnDisk() throws TaskCancelledException {   L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);   boolean loaded;   try {      loaded = downloadImage();      if (loaded) {         int width = configuration.maxImageWidthForDiskCache;         int height = configuration.maxImageHeightForDiskCache;         if (width > 0 || height > 0) {            L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);            resizeAndSaveImage(width, height); // TODO : process boolean result         }      }   } catch (IOException e) {      L.e(e);      loaded = false;   }   return loaded;}private boolean downloadImage() throws IOException {   InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());   if (is == null) {      L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);      return false;   } else {      try {         return configuration.diskCache.save(uri, is, this);      } finally {         IoUtils.closeSilently(is);      }   }}
  1. 首先从网络下载图片downloadImage
  2. 把图片存入磁盘resizeAndSaveImage(width, height);

那从网络下载图片并存入磁盘和内存的代码就分析完了,最后回到上面LoadAndDisplayImageTask.run方法,看看后面的代码。

4.13 给调用者处理图片

if (bmp != null && options.shouldPostProcess()) {   L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);   bmp = options.getPostProcessor().process(bmp);   if (bmp == null) {      L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);   }}

图片已经拿到了,所以如果需要回调处理图片的话,现在回调一次。

4.14 显示图片

DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);runTask(displayBitmapTask, syncLoading, handler, engine);

创建了一个 DisplayBitmapTask来显示图片

final class DisplayBitmapTask implements Runnable {    private final BitmapDisplayer displayer;    ...   @Override   public void run() {      if (imageAware.isCollected()) {         L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);         listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());      } else if (isViewWasReused()) {         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);      }   }}   

显示图片的时候又封装了一个displayer, displayer封装了图片显示的效果,比如圆角之类的 。

0