ImageLoader源码解析

概要

ImageLoader是最早的图片加载框架之一。虽然现在已经使用较少,但是ImageLoader的结构清晰,功能丰富,源码很具有学习的价值。

源码地址

使用方式

首先回顾一下使用ImageLoader加载图片的代码片段

DisplayImageOptions options = new DisplayImageOptions.Builder()
                    .showImageOnLoading(R.drawable.ic_stub)
                    .showImageForEmptyUri(R.drawable.ic_empty)
                    .showImageOnFail(R.drawable.ic_error)
                    .cacheInMemory(true)
                    .cacheOnDisk(true)
                    .considerExifParams(true)
                    .displayer(new CircleBitmapDisplayer(Color.WHITE, 5))
                    .build();
ImageLoader.getInstance().displayImage(uri, imageView, options, loadListener);

源码分析

基于版本1.9.5

结构分析

类结构图

class_imageloader.png

类结构特点

  1. ImageLoader是单例实现的门面类,使用者可以通过ImageLoader实现初始化配置,发起图片加载任务,取消任务,清理缓存等;
  2. 每一个加载任务,会被分解为多个步骤,每个步骤对应一个小任务,由专门的类来处理,如MemeryCache负责内存缓存,ImageDownloader负责下载,BitmapDisplayer负责展示;
  3. ImageLoaderEngine统一管理任务分发(包括异步任务入队);
  4. 用户可以通过ImageLoderConfiguration或者DisplayImageOptions,对各个步骤的具体策略进行配置, 总体思路为对所有图片任务生效的配置在ImageLoderConfiguration中,不同图片任务有差异的配置在DisplayImageOptions中。

主流程分析

加载过程图示如下:

image.png

下面仅列举一些关键步骤,部分细节略过

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

        if (TextUtils.isEmpty(uri)) {   // uri为null分支
            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
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); // 生成key
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

        listener.onLoadingStarted(uri, imageAware.getWrappedView()); // load start
        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()) {// 设置loadingImage
                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)); //构建ImageLoadingInfo
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));//构建loadAndDisplayTask
            if (options.isSyncLoading()) {
                displayTask.run();//同步加载
            } else {
                engine.submit(displayTask);//提交异步任务
            }
        }
    }
  1. checkConfiguration(); - 保证已初始化完成
  2. 赋值listener和options
  3. 处理uri =null的分支(分支内逻辑略)
  4. 计算targetSize
  5. 生成memoryCacheKey
  6. 关联imageAware.id和memoryCacheKey
  7. listener.onLoadingStarted(uri, imageAware.view)
  8. 根据memoryCacheKey从内存中读取bitmap
    8.1. 如果缓存bitmap有效 -> 9
    8.2. 如果缓存bitmap无效 -> 10
  9. 提交ProcessAndDisplayImageTask
    9.1. new ProcessAndDisplayImageTask()
    9.2. engine.submit(displayTask);
  10. 提交LoadAndDisplayImageTask
    10.1. set loading image to view,
    10.2. new ImageLoadingInfo()
    10.3. new LoadAndDisplayImageTask(imageLoadingInfo)
    10.4. engine.submit(task);

taskDistributor-execute-LoadAndDisplayImageTask

  1. load from diskCache
    1.1 image on disk -> 入队taskExecutorForCachedImages
    1.2 image not on disk -> 入队taskExecutor

LoadAndDisplayImageTask - run()

  1. wait if paused -> 可能导致线程wait
    private boolean waitIfPaused() {
        AtomicBoolean pause = engine.getPause();
        if (pause.get()) {
            synchronized (engine.getPauseLock()) {
                if (pause.get()) {
                    L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
                    try {
                        engine.getPauseLock().wait();
                    } catch (InterruptedException e) {
                        L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
                        return true;
                    }
                    L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
                }
            }
        }
        return isTaskNotActual();
    }
  1. delay if need (if options.shouldDelayBeforeLoading())-> 可能导致线程sleep
  2. loadFromUriLock.lock(); -> 申请锁
  3. 根据memoryCacheKey从内存中读取bitmap
    4.1. 如果bitmap有效 -> 6
    4.2. 如果bitmap无效 -> 5
  4. bitmap = tryLoadBitmap()
  5. preProcessor.process(bitmap)
  6. memoryCache.put(memoryCacheKey, bmp);
  7. postProcessor.process(bitmap)
  8. loadFromUriLock.unlock();
  9. new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom)
  10. taskDistributor/handler.execute(task);

LoadAndDisplayImageTask - tryLoadBitmap()

  1. load from disk
    1.1 image on disk -> decodeImage
    1.2 image not on disk -> 2
  2. downloader.getStream(uri)
  3. notify progress if needed
  4. diskCache.save(uri, inputStream)
  5. resize and save image
  6. decode image

流程总结

由于图片框架的IO请求和网络请求操作较多,所以ImageLoader维护了多个线程池来实现三级缓存,并且对重复任务(同一份资源的多次下载请求)的考虑,再各个节点均有缓存校验。

线程使用

异步任务线程池

ImageLoderConfiguration中有三个线程池

  1. taskExecutor : 执行下载图片的任务
  2. taskExecutorForCachedImages : 执行从磁盘读取缓存的任务
  3. taskDistributor : 负责分发任务,如果用户没有指定展示的handler,展示任务也会被分配到此线程

分发任务代码片段如下

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

线程池的设定

  1. taskExecutortaskExecutorForCachedImages
    默认线程池大小: DEFAULT_THREAD_POOL_SIZE = 3,核心数和最大数均为3.
    Queue支持 FIFO(first in first out),LIFO(last in first out)两种。

代码片段如下:

    /** Creates default implementation of task executor */
    public static Executor createExecutor(int threadPoolSize, int threadPriority,
            QueueProcessingType tasksProcessingType) {
        boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
        BlockingQueue taskQueue =
                lifo ? new LIFOLinkedBlockingDeque() : new LinkedBlockingQueue();
        return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
                createThreadFactory(threadPriority, "uil-pool-"));
    }
  1. taskDistributor
    使用默认cacheThreadTool(核心数0,最大无上限)

代码片段如下

/** Creates default implementation of task distributor */
    public static Executor createTaskDistributor() {
        return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-"));
    }
  1. 支持用户通过config自定义线程池

缓存算法

LruMemoryCache

LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

  1. 数据结构使用LinkedHashMap
  2. LinkedHashMap内部对于get的实现如下:
int hash = Collections.secondaryHash(key);
        HashMapEntry[] tab = table;
        for (HashMapEntry e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                if (accessOrder)
                    makeTail((LinkedEntry) e);
                return e.value;
            }
        }

即每一次查找key都从链表尾节点查询
当调用get之后,会将这个节点放到链表尾节点上

  1. LinkedHashMap对于新put的对象,也会放到尾节点上
  2. 每一次执行put(String key, Bitmap value),都会检查size是否超过maxSize

BaseMemoryCache

缓存软引用,使用HashMap

private final Map> softMap = Collections.synchronizedMap(new HashMap>());

LimitedMemoryCache

继承BaseMemoryCache,缓存使用软引用+强引用,其中强引用保存在列表LinkedList中,由子类决定removeNext具体的对象。

private final List hardCache = Collections.synchronizedList(new LinkedList());

@Override
public boolean put(String key, Bitmap value) {
        boolean putSuccessfully = false;
        // Try to add value to hard cache
        int valueSize = getSize(value);
        int sizeLimit = getSizeLimit();
        int curCacheSize = cacheSize.get();
        if (valueSize < sizeLimit) {
            while (curCacheSize + valueSize > sizeLimit) {
                Bitmap removedValue = removeNext();
                if (hardCache.remove(removedValue)) {
                    curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
                }
            }
            hardCache.add(value);
            cacheSize.addAndGet(valueSize);

            putSuccessfully = true;
        }
        // Add value to soft cache
        super.put(key, value);
        return putSuccessfully;
}

protected abstract Bitmap removeNext();

...

FIFOLimitedMemoryCache

继承LimitedMemoryCache,使用LinkedList作为queue保存强引用,当缓存达到上限,移除第一张。

@Override
protected Bitmap removeNext() {
    return queue.remove(0);
}

LargestLimitedMemoryCache

继承LimitedMemoryCache,使用HashMap保存强引用,当缓存达到上限,移除size最大的一张。

    @Override
    protected Bitmap removeNext() {
        Integer maxSize = null;
        Bitmap largestValue = null;
        Set> entries = valueSizes.entrySet();
        synchronized (valueSizes) {
            for (Entry entry : entries) {
                if (largestValue == null) {
                    largestValue = entry.getKey();
                    maxSize = entry.getValue();
                } else {
                    Integer size = entry.getValue();
                    if (size > maxSize) {
                        maxSize = size;
                        largestValue = entry.getKey();
                    }
                }
            }
        }
        valueSizes.remove(largestValue);
        return largestValue;
    }

IO

保存单文件 ( BaseDiskCache)

为了避免下载不完整,读取过程命名加后缀.tmp

    @Override
    public boolean save(String imageUri, Bitmap bitmap) throws IOException {
        File imageFile = getFile(imageUri);
        File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
        OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
        boolean savedSuccessfully = false;
        try {
            savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
        } finally {
            IoUtils.closeSilently(os);
            if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
                savedSuccessfully = false;
            }
            if (!savedSuccessfully) {
                tmpFile.delete();
            }
        }
        bitmap.recycle();
        return savedSuccessfully;
    }

LruDiskCache

LruDiskCache非google开发,但已经作为图片磁盘缓存的一种解决方案,得到了官方的认可。
google源码地址:googlesource DiskLruCache.java
详细讲解

你可能感兴趣的:(ImageLoader源码解析)