Android-Universal-Image-Loader 源码解读

Posted maplejaw_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-Universal-Image-Loader 源码解读相关的知识,希望对你有一定的参考价值。

转载请注明本文出自maplejaw的博客(http://blog.csdn.net/maplejaw_

Universal-Image-Loader是一个强大而又灵活的用于加载、缓存、显示图片的android库。它提供了大量的配置选项,使用起来非常方便。
image_1al8q6r8k4sv1v1gqll3ot1bd49.png-332.8kB

基本概念

基本使用

  1. 首次配置
    在第一次使用ImageLoader时,必须初始化一个全局配置,一般会选择在Application中配置。

    public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
    
        //为ImageLoader初始化一个全局配置
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
            ...
            .build();
        ImageLoader.getInstance().init(config);//初始化
        ...
    }
    }

    可选的所有配置如下。

    // 不要把这些拷贝到你的项目中! 这里仅仅是例举出所有可用的选项,根据自身情况进行配置。。
    File cacheDir = StorageUtils.getCacheDirectory(context);
    ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
        .memoryCacheExtraOptions(480, 800) // default = device screen ,默认为屏幕宽高 dimensions,内存缓存的最大宽高
        .diskCacheExtraOptions(480, 800, null)//磁盘缓存最大宽高,默认不限制
        .taskExecutor(...)//下载图片的线程池
        .taskExecutorForCachedImages(...);//处理缓存图片的线程池
        .threadPoolSize(3) // default //线程池数量,只在使用默认线程池有效
        .threadPriority(Thread.NORM_PRIORITY - 2) // default //线程优先级
        .tasksProcessingOrder(QueueProcessingType.FIFO) // default //队列处理策略
        .denyCacheImageMultipleSizesInMemory() //阻止内存中多尺寸缓存
        .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //内存缓存
        .memoryCacheSize(2 * 1024 * 1024) //配置缓存大小
        .memoryCacheSizePercentage(13) // default //缓存百分比
        .diskCache(new UnlimitedDiskCache(cacheDir)) // default //磁盘缓存
        .diskCacheSize(50 * 1024 * 1024) //磁盘缓存大小,只在使用默认缓存有效
        .diskCacheFileCount(100)  //磁盘缓存文件数,只在使用默认缓存有效
        .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default //key生成器
        .imageDownloader(new BaseImageDownloader(context)) // default  //图片下载器
        .imageDecoder(new BaseImageDecoder()) // default  //图片解码器
        .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default,这里配置DisplayImageOptions
        .writeDebugLogs() //打印调试日志
        .build();
  2. 配置显示图片选项
    我们可以给每一次显示图片配置一些选项,比如是否可以缓存,采样大小等等。

     // 不要把这些拷贝到你的项目中! 这里仅仅是例举出所有可用的选项,根据自身情况进行配置。。
    DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
        .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
        .showImageOnFail(R.drawable.ic_error) // resource or drawable
        .resetViewBeforeLoading(false)  // default 仅在没有配置loading占位图时生效
        .delayBeforeLoading(1000) //延时加载
        .cacheInMemory(false) // default
        .cacheOnDisk(false) // default
        .preProcessor(...) //bitmap预处理
        .postProcessor(...) //bitmap后处理
        .extraForDownloader(...) //额外的下载器
        .considerExifParams(false) // default //考虑旋转参数
        .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 默认采样方式
        .bitmapConfig(Bitmap.Config.ARGB_8888) // default  
        .decodingOptions(...) //配置解码的BitmapFactory.Options
        .displayer(new SimpleBitmapDisplayer()) // default 配置显示器
        .handler(new Handler()) // default  //配置Handler
        .build();
  3. 加载图片

    ImageLoader.getInstance().displayImage(...)//显示图片
    ImageLoader.getInstance().loadImage(...)//加载图片

使用疑问

相信这个图片加载框架是大家最熟悉而又最有疑问的。疑问如下:

  • 这个框架会不会对本地图片进行磁盘缓存?
  • 内部是怎么支持Drawable等其他类型的?
  • 怎么实现多尺寸和单尺寸缓存?
  • 怎么实现仅在wifi环境下加载图片?
  • 这个框架可以在ListView的复用中自动取消任务吗?
  • 怎么针对ListView进行优化?

源码解读

初始化全局配置

我们知道在使用ImageLoader之前,必须进行配置,那么我们就从ImageLoaderConfiguration这个类入手,该类属性如下:

public final class ImageLoaderConfiguration {
    final Resources resources;//用来加载drawable图片
    //内存缓存最大宽高,默认为屏幕尺寸
    final int maxImageWidthForMemoryCache;
    final int maxImageHeightForMemoryCache;
    //磁盘缓存最大宽高,默认为0,不做限制
    final int maxImageWidthForDiskCache;
    final int maxImageHeightForDiskCache;
    //Bitmap处理器,用来处理原始bitmap,返回一个新bitmap
    final BitmapProcessor processorForDiskCache;

    final Executor taskExecutor;//线程池,默认3个线程
    final Executor taskExecutorForCachedImages;//缓存图片线程池,默认3个线程
    //是否使用了自定义的线程池
    final boolean customExecutor;
    final boolean customExecutorForCachedImages;
    //线程池数量、优先级、排队类型(FIFO,LIFO)
    final int threadPoolSize;
    final int threadPriority;
    final QueueProcessingType tasksProcessingType;

    final MemoryCache memoryCache;//接口,内存缓存
    final DiskCache diskCache;//接口,磁盘缓存
    final ImageDownloader downloader;//图片下载器,根据url下载成流
    final ImageDecoder decoder;//图片解码器,用于将流解码成bitmap
    final DisplayImageOptions defaultDisplayImageOptions;//显示配置

    final ImageDownloader networkDeniedDownloader;//禁止网络的下载器(只从本地图片加载图片,可以用来做只在wifi下加载图片这个功能)
    final ImageDownloader slowNetworkDownloader;//慢网络的加载器

注释写的很详细,这里就不一一介绍了,我们知道构建者模式,需要使用build()来初始化,那么build()又做了什么?

        public ImageLoaderConfiguration build() {
            initEmptyFieldsWithDefaultValues();//初始化部分空值
            return new ImageLoaderConfiguration(this);//赋值
        }

可以看出,build()会对一些空值进行初始化,然后在通过ImageLoaderConfiguration的构造方法来赋值参数。ImageLoaderConfiguration的构造方法只是简单的一些赋值操作,我们就不进去看了。现在来看看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();
            }
        }
    }

初始化线程池(taskExecutor,taskExecutorForCachedImages)

根据队列排队策略,采用了不同的阻塞队列来初始化线程池。此外,可以看出核心线程数和最大线程数是一样的,在ImageLoader中默认开启3个线程。

    /** Creates default implementation of task executor */
    public static Executor createExecutor(int threadPoolSize, int threadPriority,
            QueueProcessingType tasksProcessingType) {
        //队列类型
        boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
        //队列
        BlockingQueue<Runnable> taskQueue =
                lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
        //线程池
        return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
                createThreadFactory(threadPriority, "uil-pool-"));
    }

初始化缓存(diskCache,memoryCache)

先来看下磁盘缓存,createReserveDiskCacheDir可以看出根据是否设置了磁盘缓存大小用了不同的DiskCache。当设置了缓存大小时采用LruDiskCache,LruDiskCache会单独新建一个名为uil-images的目录用来存放,UnlimitedDiskCache用于不限制缓存大小的情况,直接缓存在根目录下(当根目录不可用时,才会选择独立目录)。

    public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator,
            long diskCacheSize, int diskCacheFileCount) {
        File reserveCacheDir = createReserveDiskCacheDir(context);//创建独立缓存目录
        if (diskCacheSize > 0 || diskCacheFileCount > 0) {
            //使用独立的缓存目录
            File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);
            try {
                //如果定义了磁盘缓存大小,则返回一个LruDiskCache
                return new LruDiskCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize,
                        diskCacheFileCount);
            } catch (IOException e) {
                L.e(e);
                // continue and create unlimited cache
            }
        }
        //获取缓存根目录
        File cacheDir = StorageUtils.getCacheDirectory(context);
        //如果没有定义磁盘缓存大小,则返回一个UnlimitedDiskCache。将根目录和独立目录都传入
        return new UnlimitedDiskCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);
    }

LruDiskCache内部使用了DiskLruCache,DiskLruCache是JakeWharton开源的一个缓存库,关于DiskLruCache的使用请自行查阅资料,这里只需知道LruDiskCache中使用了DiskLruCache来进行磁盘缓存。UnlimitedDiskCache这个缓存类不用考虑磁盘缓存大小,这里也不做介绍了。此外,ImageLoader中还提供了一个LimitedAgeDiskCache可以指定缓存时间。
关于内存缓存比较简单,如果可以多尺寸缓存使用了LruMemoryCache,否则使用FuzzyKeyMemoryCache。内存缓存都是使用LruCache实现的。这里不做深究。

初始化下载器(ImageDownloader)

我们知道下载器是用来根据url来下载为InputStream。那么具体是怎么实现的呢?

    public static ImageDownloader createImageDownloader(Context context) {
        return new BaseImageDownloader(context);
    }

内部返回了BaseImageDownloader,BaseImageDownloader的核心源码如下:

    @Override
    public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            case HTTP:
            case HTTPS:
                return getStreamFromNetwork(imageUri, extra);
            case FILE:
                return getStreamFromFile(imageUri, extra);
            case CONTENT:
                return getStreamFromContent(imageUri, extra);
            case ASSETS:
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE:
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

可以看出,根据不同类型使用了不同方法,看到这相信你已经明白该库是怎么支持Drawable等其他类型的了,如果你需要支持自定义的类型,只需要重写getStreamFromOtherSource即可。我们来看看其中两种类型。

  • getStreamFromDrawable
    将Drawable转化为流

    protected InputStream getStreamFromDrawable(String imageUri, Object extra) {
        String drawableIdString = Scheme.DRAWABLE.crop(imageUri);//提取drawable://后的内容
        int drawableId = Integer.parseInt(drawableIdString);//提取id
        return context.getResources().openRawResource(drawableId);//转为InputStream
    }
  • getStreamFromNetwork

        protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        HttpURLConnection conn = createConnection(imageUri, extra);
        //..
        //省略了部分源码
        InputStream imageStream=conn.getInputStream();//获取流
    
        //..
        //省略了部分源码
        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());//将InputStream包装为ContentLengthInputStream后返回,可以获取长度。
    }

源码的思路非常清晰,如果想要扩展的话也是比较简单的。

初始化解码器(ImageDecoder)

DefaultConfigurationFactory.createImageDecoder(writeLogs)内部同样返回了一个BaseImageDecoder,解码器用来将InputStream解码成Bitmap,我们来看看内部的核心源码。

    @Override
    public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
        Bitmap decodedBitmap;
        ImageFileInfo imageInfo;//保存了图片的大小和旋转信息

        InputStream imageStream = getImageStream(decodingInfo);//获取输入流
        //..
        //省略了部分源码
         imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);//从输入流中获取大小信息和旋转信息保存起来,采用了inJustDecodeBounds
        imageStream = resetStream(imageStream, decodingInfo);//由于流不能二次读取,所有这里进行重置
        //根据获取到的大小,生成一个BitmapFactory.Options
        Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
        //根据BitmapFactory.Options来解码bitmap
        decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
        //..
        //省略了部分源码

        if (decodedBitmap == null) {
            L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
        } else {
            //如果bitmap不为空,现在对bitmap进行旋转和翻转操作(如果需要考虑旋转因素)
            decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                    imageInfo.exif.flipHorizontal);
        }
        return decodedBitmap;
    }

整个解码流程是这样的,首先从ImageDecodingInfo中获取输入流(ImageDecodingInfo内部保存了下载器,通过下载器下载成流),然后采用inJustDecodeBounds来读取宽高和Exif信息。不同于BitmapFactory.decodeFile,InputStream不能二次读取,必须重置,读取到宽高信息后,通过prepareDecodingOptions来计算采样率,然后解码返回bitmap,最后对bitmap处理Exif旋转信息。
ImageDecodingInfo的源码如下:

public class ImageDecodingInfo {

    private final String imageKey;
    private final String imageUri;
    private final String originalImageUri;
    private final ImageSize targetSize;

    private final ImageScaleType imageScaleType;//图片缩放类型,NONE(不缩放),NONE_SAFE(除非超出硬件加速的显示范围,否则不缩放),IN_SAMPLE_POWER_OF_2(2次幂缩放),IN_SAMPLE_INT(整数缩放),EXACTLY(缩放到至少宽高有一个等于目标值,原始图片小于目标大小则不缩放),EXACTLY_STRETCHED(原始图片小于目标大小仍然缩放)

    private final ViewScaleType viewScaleType;//ImageView的缩放类型(被整理成两类,FIT_INSIDE和CROP)

    private final ImageDownloader downloader;//图片下载器
    private final Object extraForDownloader;//辅助下载器

    private final boolean considerExifParams;//考虑旋转参数
    private final Options decodingOptions;//解码的BitmapFactory.Options

    public ImageDecodingInfo(String imageKey, String imageUri, String originalImageUri, ImageSize targetSize, ViewScaleType viewScaleType,
                             ImageDownloader downloader, DisplayImageOptions displayOptions) {
        this.imageKey = imageKey;
        this.imageUri = imageUri;
        this.originalImageUri = originalImageUri;
        this.targetSize = targetSize;

        this.imageScaleType = displayOptions.getImageScaleType();
        this.viewScaleType = viewScaleType;

        this.downloader = downloader;
        this.extraForDownloader = displayOptions.getExtraForDownloader();

        considerExifParams = displayOptions.isConsiderExifParams();
        decodingOptions = new Options();
        copyOptions(displayOptions.getDecodingOptions(), decodingOptions);
    }

ImageFileInfoExifInfo的源码如下,可以看出使用了ImageSize来保存宽高,ExifInfo中保存了旋转角度以及是否水平翻转等等。
image_1al6rh8q61lfje1kmre1voa1e529.png-43.9kB
读取旋转信息用了Android中的ExifInterfaceapi,由于只能从文件获取Exif信息,所以在defineImageSizeAndRotation中做了相关判断。

    protected ExifInfo defineExifOrientation(String imageUri) {
        int rotation = 0;
        boolean flip = false;
        try {
            ExifInterface exif = new ExifInterface(Scheme.FILE.crop(imageUri));
            int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);//读取旋转信息。默认为ORIENTATION_NORMAL
            switch (exifOrientation) {
                case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
                    flip = true;
                case ExifInterface.ORIENTATION_NORMAL:
                    rotation = 0;
                    break;
                case ExifInterface.ORIENTATION_TRANSVERSE:
                    flip = true;
                case ExifInterface.ORIENTATION_ROTATE_90:
                    rotation = 90;
                    break;
                case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                    flip = true;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    rotation = 180;
                    break;
                case ExifInterface.ORIENTATION_TRANSPOSE:
                    flip = true;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    rotation = 270;
                    break;
            }
        } catch (IOException e) {
            L.w("Can't read EXIF tags from file [%s]", imageUri);
        }
        return new ExifInfo(rotation, flip);
    }

最后将旋转信息应用到bitmap中。可以看出,使用了Matrix进行旋转缩放。

    protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo,
            int rotation, boolean flipHorizontal) {
        Matrix m = new Matrix();

        //获取采样错放类型
        ImageScaleType scaleType = decodingInfo.getImageScaleType();
        if (scaleType == ImageScaleType.EXACTLY || scaleType == ImageScaleType.EXACTLY_STRETCHED) {
            ImageSize srcSize = new ImageSize(subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), rotation);
            //计算缩放率
            float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo
                    .getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED);
            //缩放
            if (Float.compare(scale, 1f) != 0) {
                m.setScale(scale, scale);
                }
            }
        }
        // Flip bitmap if need
        if (flipHorizontal) {//水平翻转
            m.postScale(-1, 1);

        }
        //旋转
        if (rotation != 0) {
            m.postRotate(rotation);
        }

         //创建了一个新bitmap返回
        Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
                .getHeight(), m, true);
        if (finalBitmap != subsampledBitmap) {
            subsampledBitmap.recycle();
        }
        return finalBitmap;
    }

看到这里,我们明白了,uri通过下载器下载成InputStream,然后解码器读取图片的宽高和旋转信息,采样InputStream解码成bitmap,最后处理了旋转信息并返回。

初始化显示选项(DisplayImageOptions)

在初始化配置中使用了createSimple来创建了一个默认显示选项。

if (defaultDisplayImageOptions == null) {//创建默认的显示配置
                defaultDisplayImageOptions = DisplayImageOptions.createSimple();
            }

关于DisplayImageOptions,下一小节会详细介绍,createSimple只是直接调用了build用了默认值而已。

配置显示图片选项(DisplayImageOptions)

DisplayImageOptions同样也使用了构建者模式,按照老规矩,先来看看该类的属性。

public final class DisplayImageOptions {
     //=============各种占位图 START===============
    private final int imageResOnLoading;
    private final int imageResForEmptyUri;
    private final int imageResOnFail;
    private final Drawable imageOnLoading;
    private final Drawable imageForEmptyUri;
    private final Drawable imageOnFail;
    //=============各种占位图 END===============
    private final boolean resetViewBeforeLoading;//加载前重置
    private final boolean cacheInMemory;//内存缓存?
    private final boolean cacheOnDisk;//磁盘缓存?
    private final ImageScaleType imageScaleType;//采样缩放类型
    private final Options decodingOptions;//解码时的BitmapFactory.Options
    private final int delayBeforeLoading;//延时加载
    private final boolean considerExifParams;//考虑旋转参数
    private final Object extraForDownloader;//辅助的下载器
    //bitmap处理器接口,用来处理原始bitmap,返回一个新bitmap
    private final BitmapProcessor preProcessor;//预处理(磁盘中加载出来,放入内存之前)
    private final BitmapProcessor postProcessor;//后处理(显示之前)
    private final BitmapDisplayer displayer;//图片显示器
    private final Handler handler;//用于切换线程
    private final boolean isSyncLoading;//是否异步加载

我们知道构建者模式一般通过build来初始化,那我们来看看一些默认值。
image_1al6tvfcn6qc1nv17jj1me1fgim.png-67.2kB
可以看出,默认没有采用任何缓存策略。缩放类型采用了二次幂采样。
默认的BitmapDisplayer如下:

    /** Creates default implementation of {@link BitmapDisplayer} - {@link SimpleBitmapDisplayer} */
    public static BitmapDisplayer createBitmapDisplayer() {
        return new SimpleBitmapDisplayer();
    }

可以看出内部采用了SimpleBitmapDisplayer

public final class SimpleBitmapDisplayer implements BitmapDisplayer {
    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        imageAware.setImageBitmap(bitmap);
    }
}

ImageAware保存View的宽高、View的哈希值标识以及View本身等信息,主要用来将图像设置到控件中。
LoadedFrom是一个枚举类,用来标识从内存、磁盘、网络中加载。

此外,还有FadeInBitmapDisplayer、RoundedBitmapDisplayer、CircleBitmapDisplayer等等。
CircleBitmapDisplayer的源码如下,可以看出唯一不同的是加载了CircleDrawable(自定义的Drawable类,使用BitmapShader来切圆),只要你喜欢,你可以自定义出各种各样形状的显示器。

    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        if (!(imageAware instanceof ImageViewAware)) {
            throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
        }

        imageAware.setImageDrawable(new CircleDrawable(bitmap, strokeColor, strokeWidth));
    }

用ImageAware包装的好处在于内部使用了弱引用,可以避免内存泄漏。

加载/显示图片(loadImage/displayImage)

终于讲到正题了——加载/显示图片,我们来看看ImageLoader是怎么将下载器、解码器、显示器等结合起来了的吧。在分析之前,来认识一下ImageLoader这个类中的属性。
image_1al6vp8bs1ici1papuek1rho1vmd13.png-40kB
出乎意料的简洁,getInstance采用了单例模式。ImageLoadingListener加载监听大家应该很清楚,这里不做赘述。ImageLoaderConfiguration也已经介绍过了。但是ImageLoaderEngine这个是什么鬼呢?
大家还记得 ImageLoader.getInstance().init(config);//初始化这一句吗?没错,将ImageLoaderConfiguration传入了进去。

     */
    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);//用ImageLoaderEngine包装了起来
            this.configuration = configuration;//同时也赋值给configuration一份
        } else {
            L.w(WARNING_RE_INIT_CONFIG);
        }
    }

可以看出ImageLoaderEngine用来包装了ImageLoaderConfiguration。那么ImageLoaderEngine到底是来干嘛的?既然取名为ImageLoader引擎,可以想象到其核心地位。ImageLoaderEngine主要负责执行加载和显示图片等任务的引擎(LoadAndDisplayImageTask,ProcessAndDisplayImageTask)。

该类属性如下。

class ImageLoaderEngine {

    final ImageLoaderConfiguration configuration;//配置

    private Executor taskExecutor;//任务执行者(下载图片的线程池)
    private Executor taskExecutorForCachedImages;//处理缓存的线程池
    private Executor taskDistributor;//任务分配者(由它来控制把任务往哪个线程池提交)

    private final Map<Integer, String> cacheKeysForImageAwares = Collections
            .synchronizedMap(new HashMap<Integer, String>());//key为View的哈希值,value为请求的网址(后面会追加宽高)
    private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();//uri锁map

    private final AtomicBoolean paused = new AtomicBoolean(false);
    private final AtomicBoolean networkDenied = new AtomicBoolean(false);
    private final AtomicBoolean slowNetwork = new AtomicBoolean(false);

    private final Object pauseLock = new Object();//暂停锁

    ImageLoaderEngine(ImageLoaderConfiguration configuration) {
        this.configuration = configuration;

        taskExecutor = configuration.taskExecutor;
        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;

        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
    }
    //..
    //省略部分源码

}

ImageLoaderEngine有两个提交方法。一种处理本地/磁盘加载,一种处理内存加载。

    //
    void submit(final LoadAndDisplayImageTask task) {
        taskDistributor.execute(new Runnable() {
            @Override
            public void run() {
                //首先磁盘中获取
                File image = configuration.diskCache.get(task.getLoadingUri());
                boolean isImageCachedOnDisk = image != null && image.exists();
                initExecutorsIfNeed();
                if (isImageCachedOnDisk) {
                    //如果磁盘存在就提交到缓存线程池
                    taskExecutorForCachedImages.execute(task);
                } else {
                    //提交到下载线程池
                    taskExecutor.execute(task);
                }
            }
        });
    }

    /** Submits task to execution pool */
    //ProcessAndDisplayImageTask提交到缓存线程池
    void submit(ProcessAndDisplayImageTask task) {
        initExecutorsIfNeed();
        taskExecutorForCachedImages.execute(task);
    }

显示图片(displayImage)

现在再来看看平时用的最多的displayImage吧。

    public void displayImage(String uri, ImageView imageView) {
        //用ImageViewAware包装ImageView
        displayImage(uri, new ImageViewAware(imageView), null, null, null);
    }

可以看出,用ImageViewAware包装了ImageView,displayImage最终调用的重载方法如下

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

        checkConfiguration();//检查ImageLoaderConfiguration有没有初始化。
        if (imageAware == null) {//ImageAware不可为空
            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
        }
        if (listener == null) {//加载监听
            listener = defaultListener;
        }
        if (options == null) {//显示选项
            options = configuration.defaultDisplayImageOptions;
        }

       //=================如果是个空url直接设置占位图 START====
        if (TextUtils.isEmpty(uri)) {
            engine.cancelDisplayTaskFor(imageAware);//引擎取消显示任务(从map中移除)
            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;//返回
        }

        //=================如果是个空url直接设置占位图 END====

        if (targetSize == null) {//如果没有定义显示目标大小,就根据ImageView自动获取
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware,configuration.getMaxImageSize());
        }
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);//生成内存缓存的key(`[imageUri]_[width]x[height]`的形式)

        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);//引擎准备显示任务(放入map中)

        listener.onLoadingStarted(uri, imageAware.getWrappedView());//加载开始监听

        //=================从内存中取 START====
        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//从内存中取
        if (bmp != null && !bmp.isRecycled()) {//如果内存中取到
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

            if (options.shouldPostProcess()) {//是否需要后处理?
                //engine.getLockForUri(uri),获取当前url的锁
                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
                //ProcessAndDisplayImageTask是一个Runable对象,处理再显示
                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
                if (options.isSyncLoading()) {//如果是同步加载,则直接执行Runable中的run()方法
                    displayTask.run();
                } else {
                    engine.submit(displayTask);//异步加载,直接使用引擎提交到线程池中
                }
            } else {
                //如果不需要后处理bitmap,直接获取BitmapDisplayer进行显示
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);//加载完成监听
            }
        } else {

   //=================从内存中取 END====
   //=================从磁盘/网络中取 START====
           //内存中没有
            if (options.shouldShowImageOnLoading()) {//设置占位图
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                imageAware.setImageDrawable(null);
            }
            //engine.getLockForUri(uri),获取当前url的锁
            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                    options, listener, progressListener, engine.getLockForUri(uri));
            //LoadAndDisplayImageTask也是一个Runable对象,加载然后显示
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));
            if (options.isSyncLoading()) {//如果是同步,直接执行run
                displayTask.run();
            } else {
                engine.submit(displayTask);//否则通过引擎提交到线程池中
            }
        }

//=================从磁盘/网络中取 END====
    }

源码有点长,我们慢慢来。引擎取消任务和准备任务的源码如下。

    void cancelDisplayTaskFor(ImageAware imageAware) {
        cacheKeysForImageAwares.remove(imageAware.getId());//从map中移除
    }

   void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
        //key为View的hashcode,value为请求url(加上宽高)
        //保存到map中
        cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
    }

ImageLoadingInfo用于保存图片加载时所需要的信息

final class ImageLoadingInfo {

    final String uri;//原始的url
    final String memoryCacheKey; //加上宽高的url
    final ImageAware imageAware;
    final ImageSize targetSize;
    final DisplayImageOptions options;
    final ImageLoadingListener listener;
    final ImageLoadingProgressListener progressListener;
    final ReentrantLock loadFromUriLock; //uri锁

    //构造方法中会传入url锁
    public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey,
            DisplayImageOptions options, ImageLoadingListener listener,
            ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) {
        this.uri = uri;
        this.imageAware = imageAware;
        this.targetSize = targetSize;
        this.options = options;
        this.listener = listener;
        this.progressListener = progressListener;
        this.loadFromUriLock = loadFromUriLock;
        this.memoryCacheKey = memoryCacheKey;
    }
}

如果内存缓存中存在bitmap,此时应该使用ProcessAndDisplayImageTask,ProcessAndDisplayImageTask是一个Runable对象,从名字可以看出,这个任务主要处理bitmap然后进行显示。run方法如下:


    @Override
    public void run() {
        以上是关于Android-Universal-Image-Loader 源码解读的主要内容,如果未能解决你的问题,请参考以下文章