UniversalImageLoader 详解

-


配置

ImageLoaderConfiguration中Builder的全部配置方法

ImageLoaderConfiguration

  • memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxImageHeightForMemoryCache)
内存缓存的图片最大宽高
  • discCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache,
    BitmapProcessor processorForDiskCache)
已弃用,建议使用下一个方法。设置磁盘缓存的图片最大宽高
BitmapProcessor包含一个Bitmap process(Bitmap bitmap)方法,在缓存前处理Bitmap
  • diskCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache,
    BitmapProcessor processorForDiskCache)
同上,替代方案
  • taskExecutor(Executor executor)
自定义线程池
  • taskExecutorForCachedImages(Executor executorForCachedImages)
自定义加载已缓存在磁盘上的图片的线程池,因为这样的任务处理较快
如果也使用前一个方法的线程池的话可能会导致因等待而延长执行时间
  • threadPoolSize(int threadPoolSize)
设置线程池
  • threadPriority(int threadPriority)
设置线程优先级
  • denyCacheImageMultipleSizesInMemory()
设置同一张图片在不同尺寸下是否创建多分缓存
  • tasksProcessingOrder(QueueProcessingType tasksProcessingType)
设置处理顺序,LIFO或FIFO
  • memoryCacheSize(int memoryCacheSize)
设置内存缓存的大小
  • memoryCacheSizePercentage(int availableMemoryPercent)
设置内存缓存的大小,使用百分比
  • memoryCache(MemoryCache memoryCache)
设置缓存管理对象,默认使用LruMemoryCache
  • discCacheSize(int maxCacheSize)
设置磁盘缓存大小,已弃用
  • diskCacheSize(int maxCacheSize)
同上,替代方案
  • discCacheFileCount(int maxFileCount)
设置磁盘缓存文件最大数量,已弃用
  • diskCacheFileCount(int maxFileCount)
同上,替代方案
  • discCacheFileNameGenerator(FileNameGenerator fileNameGenerator)
设置缓存文件的名称生成工具,已弃用
  • diskCacheFileNameGenerator
同上,替代方案
  • discCache(DiskCache diskCache)
设置磁盘缓存管理对象,已弃用
  • diskCache(DiskCache diskCache)
同上,替代方案
  • imageDownloader(ImageDownloader imageDownloader)
设置图片下载对象
  • imageDecoder(ImageDecoder imageDecoder)
设置图片解码对象
  • defaultDisplayImageOptions(DisplayImageOptions defaultDisplayImageOptions)
设置显示图片的策略
  • writeDebugLogs()
设置是否输出调试日志

DisplayImageOptions

  • showStubImage(int imageRes)
显示占位图,已弃用
  • showImageOnLoading(int imageRes)
同上,替代方案
  • showImageOnLoading(Drawable drawable)
同上,替代方案
  • showImageForEmptyUri(int imageRes)
空或null的url使用的图片
  • showImageForEmptyUri(Drawable drawable)
同上,替代方案
  • showImageOnFail(int imageRes)
加载失败时使用的图片
  • showImageOnFail(Drawable drawable)
同上,替代方案
  • cacheInMemory()
设置是否使用内存缓存
  • cacheOnDisc()
设置是否使用磁盘缓存
  • imageScaleType(ImageScaleType imageScaleType)
设置图片缩放类型
  • bitmapConfig(Bitmap.Config bitmapConfig)
设置bitmapConfig
  • decodingOptions(Options decodingOptions)
设置图片解码类型
  • delayBeforeLoading(int delayInMillis)
设置延迟加载的时间
  • considerExifParams
设置是否处理EXIF信息
  • preProcessor(BitmapProcessor preProcessor)
设置预处理器实例
  • postProcessor(BitmapProcessor postProcessor)
设置后处理器实例
  • displayer(BitmapDisplayer displayer)
设置图片现实管理对象实例
  • Builder handler(Handler handler)
设置handler


使用

  • uil中加载图片的方法分为三类,displayImage,参数中包含ImageView或ImageAware这样的图片直接接受者,loadImage,参数不包含直接接受者,但包含回调接口,也就是只负责加载不负责显示,loadImageSync,同步,直接返回bitmap
  • 涉及到的参数主要包括
ImageAware:显示图片的对象,包装ImageView等
ImageLoadingListener:包含开始失败完成取消的回调接口
ImageLoadingProgressListener:加载进度回调


实现


主要模块

  • uil的分包还是很清晰的,主要包括了
utils:包括一些工具类
cache包:包括disc和memory两部分,负责磁盘和内存缓存
core包:核心功能包括以下部分
assist:主要包括一些枚举和队列的实现
decode:图片解码,将流转换为Bitmap,主要实现为BaseImageDecoder
BitmapDisplayer:图片展示,所有类继承自BitmapDisplayer,负责图片处理如圆角,动画,然后设置图片到imageAware上
download:负责网络,文件或资源图片的加载
imageaware:代表显示图片的类,如ImageView或普通View等
listener:回调接口
process:对Bitmap进行预处理或后处理
DefaultConfigurationFactory:生成默认option,如线程池,缓存管理实例等
DisplayBitmapTask:展示图片的runnable
LoadAndDisplayImageTask:加载并显示图片的runnable
ProcessAndDisplayImageTask:处理并显示图片的runnable
ImageLoaderEngine:任务分发器,负责分发各类task给具体的线程池去执行


主要流程

  • 图片加载按顺序包含以下流程,注意并不是全部都会执行
1.ImageDownloader 下载图片(没有缓存,从这里开始)
2.DiskCache 磁盘缓存
3.ImageDecoder 解码流为Bitmap(只有磁盘缓存,从这里开始)
4.BitmapProcessor 预处理
5.MemoryCache 内存缓存
6.BitmapProcessor 后处理(有内存缓存,从这里开始)
7.BitmapDisplayer 显示图片


加载实现

  • 加载方法包括loadImageSync,loadImage和displayImage,loadImageSync通过loadImage实现,loadImage通过displayImage来实现,只不过前两者使用的imageAware对象为NonViewAware类实例,它的相关显示方法为空实现
public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
        ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
    checkConfiguration();
    if (targetImageSize == null) {
        targetImageSize = configuration.getMaxImageSize();
    }
    if (options == null) {
        options = configuration.defaultDisplayImageOptions;
    }

    NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
    displayImage(uri, imageAware, options, listener, progressListener);
}
  • 核心方法displayImage实现如下
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
    //1.检查配置和回调
    checkConfiguration();
    if (imageAware == null) {
        throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
    }
    if (listener == null) {
        listener = defaultListener;
    }
    if (options == null) {
        options = configuration.defaultDisplayImageOptions;
    }

    //2. uri判空,为空则选择性显示占位图,这个过程中加载和完成的回调会被调用
    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;
    }

    //3.获取显示目标的大小
    if (targetSize == null) {
        targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
    }
    //4.根据uri生成内存缓存的key
    String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);

    //5.将imageAware的id和key的键值对放入缓存
    engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

    //6.调用开始加载回调
    listener.onLoadingStarted(uri, imageAware.getWrappedView());

    //7.从内存缓存获取bitmap,获取成功开始后处理
    Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
    if (bmp != null && !bmp.isRecycled()) {
        L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

        //使用ProcessAndDisplayImageTask,同步则直接调用run,否则提交线程池
        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);
            }

        //不需要后处理直接调用Displayer显示,默认实现是直接setBitmap
        } else {
            options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
        }

    //8.内存没有,从磁盘或网络获取,使用LoadAndDisplayImageTask,同步则直接调用run,否则提交线程池
    } 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);
        }
    }
}
  • ProcessAndDisplayImageTask的run方法,先调用BitmapProcessor处理,然后使用DisplayBitmapTask投递runnable到handler
@Override
public void run() {
    L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);

    BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
    Bitmap processedBitmap = processor.process(bitmap);
    DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
            LoadedFrom.MEMORY_CACHE);
    LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
  • LoadAndDisplayImageTask的run方法,在次从内存缓存中去读取 bitmap 对象,若 bitmap 对象不存在,则调用 tryLoadBitmap() 从磁盘或网络获取 bitmap 对象,获取成功后若在 DisplayImageOptions.Builder 中设置了 cacheInMemory(true), 同时将 bitmap 对象缓存到内存中,最后新建DisplayBitmapTask使用handler显示图片
@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);
}
  • tryLoadBitmap,如果磁盘中存在文件,则decodeImage解码为bitmap,否则使用tryCacheImageOnDisk从网络获取并保存到磁盘,这里的ImageDownloader分几种,正常的,网络状况不好的,无网络的,正常情况下使用BaseImageDownloader,基于HttpURLConnection实现,后两者这都是包装了BaseImageDownloader,其中SlowNetworkDownloader主要特性是使用的InputStream重写了skip方法,NetworkDeniedImageDownloader则是在获取流时抛出IllegalStateException,因为网络不可用
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;
}

private ImageDownloader getDownloader() {
    ImageDownloader d;
    if (engine.isNetworkDenied()) {
        d = networkDeniedDownloader;
    } else if (engine.isSlowNetwork()) {
        d = slowNetworkDownloader;
    } else {
        d = downloader;
    }
    return d;
}
  • 注意BaseImageDownloader实现上不止负责网络图片的获取,具体获取什么图片由Uri决定,只不过它都封装为stream
public class BaseImageDownloader implements ImageDownloader {
    @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);
        }
    }
}


MemoryCache

  • 不是所有缓存策略都有最大容量的限制,在有限制的情况下,默认为最大可用内存的8分之1,可设置
  • 内存策略,8种,但常用的不多,一般情况下其实只有LruMemoryCache是合适的,其他大多数是LimitedMemoryCache的子类,LimitedMemoryCache内部的强引用map使得子类返回的弱引用无意义,有的子类满容删除时是锁住和遍历map的entry,对于图片较多的应用效率很低,以下介绍这8种,注意MemoryCache是uil中的缓存类的接口,BaseMemoryCache是抽象基类,其子类LimitedMemoryCache也是抽象基类,但不是所有策略都由基类实现,因为这些基类实现得不是很好,只是保留着而已,我们使用的默认策略就是直接实现MemoryCache接口的,效果会好一些
  • 1.LimitedAgeMemoryCache:MemoryCache接口直接实现,使用HashMap记录存入的时间,get的时候,判断时间是否大于maxAge,如果是,那么就删掉这个缓存
  • 2.LruMemoryCache:MemoryCache接口直接实现,自己实现的LruCache,准确的说是删除了sdk中LruCache中的一些统计缓存信息的方法,这里体现了和picasso的抉择不同,其实这一块虽然有用,但真的没什么人用
  • 3.FuzzyKeyMemoryCache:MemoryCache接口直接实现,不过是个包装类,内部还有一个MemoryCache引用,这个类接收一个Comparator对象,在put时比较是否已经存在Comparator认为相同的key,如果存在,删除旧的,提供一种减少无用缓存的机制,使用场景只适合原来key的图片不再使用的情况,因为get时依然是通过key获取的,如果,被删除了,还需要去重新获取
  • 4.WeakMemoryCache:一个BaseMemoryCache的实现类,BaseMemoryCache中使用Reference管理bitmap,没有容量限制,WeakMemoryCache将其限制为使用弱引用
  • 5.FIFOLimitedMemoryCache:一个LimitedMemoryCache的实现类,LimitedMemoryCache是BaseMemoryCache的另一个实现,限制了容量大小,但容量满时如何删除则没有实现,FIFOLimitedMemoryCache就将其时间为FIFO的,通过LinkedList,这个cache返回弱引用
  • 6.LRULimitedMemoryCache:同理,实现了LimitedMemoryCache,使用LinkedHashMap实现LRU机制,这个cache返回弱引用
  • 7.UsingFreqLimitedMemoryCache:同理,实现了LimitedMemoryCache,删除时会删除使用频率最小的bitmap,使用hashmap实现,hashmap记录了使用次数,所以所谓频率只是使用次数,并没有这么智能,减容效率也不高需要遍历entry查找应该丢弃的bitmap,这个cache返回弱引用
  • 8.LargestLimitedMemoryCache:同理,实现了LimitedMemoryCache,使用hashmap记录大小,删除时遍历找出最大的bitmap删除,这个cache返回弱引用


DiskCache

  • BaseDiskCache: 抽象基类,实现了一些文件读写的逻辑,对缓存不设限
  • 可用的磁盘缓存策略有三种
  • 1.UnlimitedDiskCache:其实就是BaseDiskCache,继承之后什么都没干,也就不设限了
  • 2.LimitedAgeDiskCache:在get时获取文件的最后修改时间,记录到map中,并且如果和当前时间相差较大,则删除缓存,个人觉得在获取图片时删除缓存是本末倒置的,但是由于没有像DiskLruCache那样实现一个journal文件,所以也干不了别的事情
  • 3.LruDiskCache:限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素。DiskLruCache,限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素。通过缓存目录下名为journal的文件记录缓存的所有操作,并在缓存open时读取journal的文件内容存储到LinkedHashMap\ lruEntries中,后面get(String key)获取缓存内容时,会先从lruEntries中得到图片文件名返回文件。LRU 的实现跟上面内存缓存类似,lruEntries为new LinkedHashMap(0, 0.75f, true),LinkedHashMap 第三个参数表示是否需要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时trimToSize()函数始终删除第一个元素,即始终删除最近最少访问的文件



本篇完,另外看到一篇比较使用的零碎问题整理,详见