美文网首页Android源码分析
ImageLoader源码解析

ImageLoader源码解析

作者: 麦崎 | 来源:发表于2018-07-11 18:51 被阅读9次

    概要

    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<Runnable> taskQueue =
                    lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
            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<K, V>[] tab = table;
            for (HashMapEntry<K, V> 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<K, V>) e);
                    return e.value;
                }
            }
    
    

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

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

    BaseMemoryCache

    缓存软引用,使用HashMap

    private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());
    

    LimitedMemoryCache

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

    private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());
    
    @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<Bitmap, Integer>保存强引用,当缓存达到上限,移除size最大的一张。

        @Override
        protected Bitmap removeNext() {
            Integer maxSize = null;
            Bitmap largestValue = null;
            Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();
            synchronized (valueSizes) {
                for (Entry<Bitmap, Integer> 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源码解析

        本文链接:https://www.haomeiwen.com/subject/ncohpftx.html