2

本文仅对UIL中一些简单的用例做解析

画出源码目录树

首先,用脚本生成了该项目源码的目录树,然后大致浏览一下文件内容,猜测其作用:

-[ java ]
  -[ com ]
    -[ nostra13 ]
      -[ universalimageloader ]
        -[ cache ]
          -[ disc ]
            |- DiskCache.java
            |
            -[ impl ]
              |- BaseDiskCache.java
              |- LimitedAgeDiskCache.java
              |- UnlimitedDiskCache.java
              |
              -[ ext ]
                |- DiskLruCache.java
                |- LruDiskCache.java
                |- StrictLineReader.java
                |- Util.java
                |
            -[ naming ]
              |- FileNameGenerator.java
              |- HashCodeFileNameGenerator.java
              |- Md5FileNameGenerator.java
              |
          -[ memory ]
            |- BaseMemoryCache.java
            |- LimitedMemoryCache.java
            |- MemoryCache.java
            |
            -[ impl ]
              |- FIFOLimitedMemoryCache.java
              |- FuzzyKeyMemoryCache.java
              |- LargestLimitedMemoryCache.java
              |- LimitedAgeMemoryCache.java
              |- LRULimitedMemoryCache.java
              |- LruMemoryCache.java
              |- UsingFreqLimitedMemoryCache.java
              |- WeakMemoryCache.java
              |
        -[ core ]
          |- DefaultConfigurationFactory.java
          |- DisplayBitmapTask.java
          |- DisplayImageOptions.java
          |- ImageLoader.java
          |- ImageLoaderConfiguration.java
          |- ImageLoaderEngine.java
          |- ImageLoadingInfo.java
          |- LoadAndDisplayImageTask.java
          |- ProcessAndDisplayImageTask.java
          |
          -[ assist ]
            |- ContentLengthInputStream.java
            |- FailReason.java
            |- FlushedInputStream.java
            |- ImageScaleType.java
            |- ImageSize.java
            |- LoadedFrom.java
            |- QueueProcessingType.java
            |- ViewScaleType.java
            |
            -[ deque ]
              |- BlockingDeque.java
              |- Deque.java
              |- LIFOLinkedBlockingDeque.java
              |- LinkedBlockingDeque.java
              |
          -[ decode ]
            |- BaseImageDecoder.java
            |- ImageDecoder.java
            |- ImageDecodingInfo.java
            |
          -[ display ]
            |- BitmapDisplayer.java
            |- CircleBitmapDisplayer.java
            |- FadeInBitmapDisplayer.java
            |- RoundedBitmapDisplayer.java
            |- RoundedVignetteBitmapDisplayer.java
            |- SimpleBitmapDisplayer.java
            |
          -[ download ]
            |- BaseImageDownloader.java
            |- ImageDownloader.java
            |
          -[ imageaware ]
            |- ImageAware.java
            |- ImageViewAware.java
            |- NonViewAware.java
            |- ViewAware.java
            |
          -[ listener ]
            |- ImageLoadingListener.java
            |- ImageLoadingProgressListener.java
            |- PauseOnScrollListener.java
            |- SimpleImageLoadingListener.java
            |
          -[ process ]
            |- BitmapProcessor.java
            |
        -[ utils ]
          |- DiskCacheUtils.java
          |- ImageSizeUtils.java
          |- IoUtils.java
          |- L.java
          |- MemoryCacheUtils.java
          |- StorageUtils.java

从常用case入手,推断其项目架构

官网上给出的最简单的使用例子如下所示:

ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance

// Load image, decode it to Bitmap and display Bitmap in ImageView (or any other view 
//  which implements ImageAware interface)
imageLoader.displayImage(imageUri, imageView);

// Load image, decode it to Bitmap and return Bitmap to callback
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        // Do whatever you want with Bitmap
    }
});

// Load image, decode it to Bitmap and return Bitmap synchronously
Bitmap bmp = imageLoader.loadImageSync(imageUri);

下面一步步进行分析。

ImageLoader

看到 ImageLoader.getInstance()这一句,应该能马上认出这是一个singleton。代码如下所示:

public class ImageLoader {

    ...

    private volatile static ImageLoader instance;

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

    protected ImageLoader() {
    }

构造函数是空的。volatile关键字的解释可以看这里

DisplayImage

接下来到imageLoader.displayImage(imageUri, imageView);这一句。其源码如下所示:

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

注意到ImageView被包装成了ImageViewAwareImageViewAware继承于ImageAware,如下所示:

<ImageAware> {
    getWidth();
    getHeight();
    getScaleType();
    getWrappedView();
    isCollected();
    getId();
    setImageDrawable();
    setImageBitmap();
}

一路调用到如下所示方法:

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

其源代码如下所示,一边猜想它的调用逻辑,一边在关键点写下注释:

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;  // 默认是SimpleImageLoadingListener,是ImageLoadingListener的空实现
    }
    if (options == null) {
        options = configuration.defaultDisplayImageOptions; //在init()中初始化
    }

    //2. 如果uri为空,则取消对应imageAware的显示
    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) {
        // 什么是target?
        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()) {// 4. 如果hit cache,则直接显示
        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 {// 5. 如果miss cache,就去下载它
        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);
        }
    }
}

代码流程见文中的注释。可以看出三个需要重点研究的对象:Cache,Engine和Task,先说后面两个。

Engine

ImageLoaderEngine

首先看一下Engine的初始化流程。Enginer在ImageLoader的init中被实例化:

engine = new ImageLoaderEngine(configuration);

初始化代码如下所示:

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

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

    taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}

可以看到新出现的角色:Executor。这是一个接口,代码如下所示:

public interface Executor {
    void execute(java.lang.Runnable runnable);
}

可以合理猜想这是一个类似Runnable的接口,包装了在某个线程中执行的业务。

prepareDisplayTaskFor

再来看看engine的prepareDisplayTaskFor()方法:

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

还有与之对应的:

void cancelDisplayTaskFor(ImageAware imageAware) {
    cacheKeysForImageAwares.remove(imageAware.getId());
}

还有:

String getLoadingUriForView(ImageAware imageAware) {
    return cacheKeysForImageAwares.get(imageAware.getId());
}

其中 cacheKeysForImageAwares 定义如下:

private final Map<Integer, String> cacheKeysForImageAwares = Collections
            .synchronizedMap(new HashMap<Integer, String>());

可见prepareDisplayTaskFor保存了image可memory cache的key的映射关系。猜想是用于快速判断当前image是否有正在被处理。

sumit

再来看看engine的sumit方法。按字面意义猜想是将一个task放到一个调度队列中按照指定的策略执行。

/** Submits task to execution pool */
void submit(ProcessAndDisplayImageTask task) {
    initExecutorsIfNeed();
    taskExecutorForCachedImages.execute(task);
}

private void initExecutorsIfNeed() {
    if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
        taskExecutor = createTaskExecutor();
    }
    if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
            .isShutdown()) {
        taskExecutorForCachedImages = createTaskExecutor();
    }
}

private Executor createTaskExecutor() {
    return DefaultConfigurationFactory
            .createExecutor(configuration.threadPoolSize, configuration.threadPriority,
            configuration.tasksProcessingType /* FIFO, LIFO */);
}

逻辑比较简单,先看一下DefaultConfigurationFactory的createExecutor方法:

/** 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-"));
}

可以看到,创建一个executor对象需要指定线程池大小,线程优先级,队列性质(FIFO/LIFO)和一个负责新建线程的工厂类。

ThreadPoolExecutor、Executor这些都是java concurrent包内置的类,详细使用方法可见[这篇文章](),这里不展开。

由此可以看出,engine中维护着executor,executor负责根据预先设置好的调度策略执行task。

ProcessAndDisplayImageTask

以ProcessAndDisplayImageTask为例,先回忆一下调用代码:

ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));

engine,bmp这两个没什么好说的,重点放在后两者。ImageLoadingInfo是一个Data Object,保存着这次Task的所有必要信息:

final class ImageLoadingInfo {

    final String uri;
    final String memoryCacheKey;
    final ImageAware imageAware;
    final ImageSize targetSize;
    final DisplayImageOptions options;
    final ImageLoadingListener listener;
    final ImageLoadingProgressListener progressListener;
    final ReentrantLock loadFromUriLock;
    ...
}

defineHandler的代码如下所示:

private static Handler defineHandler(DisplayImageOptions options) {
    Handler handler = options.getHandler();
    if (options.isSyncLoading()) { //如果是同步加载图片(堵塞),那么这个handler不起作用,设为null
        handler = null;
    } else if (handler == null && Looper.myLooper() == Looper.getMainLooper()) {
        handler = new Handler(); // 没有指定Handler的情况下,只有主线程才能执行显示任务。
    }
    return handler;
}

这个方法的作用是返回一个执行显示bitmap动作的handler。然后我们回到ProcessAndDisplayImageTask:

final class ProcessAndDisplayImageTask implements Runnable {

    private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";

    private final ImageLoaderEngine engine;
    private final Bitmap bitmap;
    private final ImageLoadingInfo imageLoadingInfo;
    private final Handler handler;

    ...

}

ProcessAndDisplayImageTask继承Runnable接口。当engine执行sumit时,ProcessAndDisplayImageTask的run方法会被调度执行。

@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); // 指定了DisplayBitmapTask的来源
    LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}

其中PostProcessor为后置处理类,可以利用这个类对已载入的bitmap进行处理(ProcessAndDisplayImageTask中bitmap已经加载完成了)。处理完后将bitmap转交给DisplayBitmapTask类继续进行处理。下面进入LoadAndDisplayImageTask.runTask()方法:

static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
    if (sync) {
        r.run();
    } else if (handler == null) {
        engine.fireCallback(r);
    } else {
        handler.post(r);
    }
}

可以看到,如果是同步显示的,就地执行DisplayBitmapTask的run方法。如果没有指定handler,则在engine中执行,否则在指定的handler中执行。DisplayBitmapTask。

DisplayBitmapTask

其源码如下所示:

final class DisplayBitmapTask implements Runnable {

    ...

    private final Bitmap bitmap;
    private final String imageUri;
    private final ImageAware imageAware;
    private final String memoryCacheKey;
    private final BitmapDisplayer displayer;
    private final ImageLoadingListener listener;
    private final ImageLoaderEngine engine;
    private final LoadedFrom loadedFrom;

    public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, ImageLoaderEngine engine,
            LoadedFrom loadedFrom) {
        this.bitmap = bitmap;
        imageUri = imageLoadingInfo.uri;
        imageAware = imageLoadingInfo.imageAware;
        memoryCacheKey = imageLoadingInfo.memoryCacheKey;
        displayer = imageLoadingInfo.options.getDisplayer();
        listener = imageLoadingInfo.listener;
        this.engine = engine;
        this.loadedFrom = loadedFrom;
    }

    @Override
    public void run() {
        ...
    }

    /** Checks whether memory cache key (image URI) for current ImageAware is actual */
    private boolean isViewWasReused() {
        ...
    }
}

重点看其中的run方法:

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

首先,代码判断bitmap是否需要显示,如果不需要,则回调onLoadingCancelled。否则交给displayer去显示bitmap。最后再engine中清除imageAware的记录,并回调onLoadingComplete。

在这里遇到两个比较困惑的地方:isCollected和isViewWasReused。因为满足这两个条件都不需要执行display,代表image此时是collected和view已经reused了。

collected

当displayTask在执行时发现imageAware已经被回收了(GC或者别的原因),就会跳过显示这个bitmap。

isViewWasReused

isViewWasReused的代码如下所示:

/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
    String currentCacheKey = engine.getLoadingUriForView(imageAware);
    return !memoryCacheKey.equals(currentCacheKey);
}

isViewWasReused返回true则说明当前task的uri不是engine对这个imageAware最后load的uri。也就是说用户在前一个uri还没有完全载入的时候,又对相同imageAware发起了load task。因为以最后一次意图加载的uri为准,所以该次task跳过显示bitmap。

接下来研究一下BitmapDisplayer这个类。BitmapDisplayer在DisplayImageOptions的Builder子类中赋值:

private BitmapDisplayer displayer = DefaultConfigurationFactory.createBitmapDisplayer();

最后追踪到:

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

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

可见BitmapDisplayer只是简单地调用了setImageBitmap方法:

public class ImageViewAware extends ViewAware {
    
    ...
    
    @Override
    protected void setImageBitmapInto(Bitmap bitmap, View view) {
        ((ImageView) view).setImageBitmap(bitmap);
    }
}

LoadAndDisplayImageTask

当bitmap的memory cache存在时,运行ProcessAndDisplayImageTask,否则运行LoadAndDisplayImageTask。LoadAndDisplayImageTask涉及到网络下载和缓存策略,重点分析其中的run方法:

@Override
public void run() {
    if (waitIfPaused()) return; //若engine当前处于Pause状态,则等待其Resume
    if (delayIfNeed()) return; //是否需要显示一下loading image(防止闪烁)

    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(); //对一个loadingInfo上锁
    Bitmap bmp;
    try {
        checkTaskNotActual(); //检查bitmap是否被回收和当前uri是否与imageAware要显示的uri一致

        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()) { //前置处理,影响cache
                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); //将bitmap放到memory cache中
            }
        } 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);//后置处理,不影响cache
            if (bmp == null) {
                L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
            }
        }
        checkTaskNotActual();
        checkTaskInterrupted(); //检查当前thread是否被interrupt了
    } catch (TaskCancelledException e) {
        fireCancelEvent();
        return;
    } finally {
        loadFromUriLock.unlock(); //解锁
    }
    
    //见 ProcessAndDisplayImageTask 关于这段代码的解析
    DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
    runTask(displayBitmapTask, syncLoading, handler, engine);
}

代码执行流程方面的解析见中文注释,值得注意的有:

  1. waitIfPaused()和delayIfNeed()方法返回的时候都调用了isTaskNotActual()方法。这个方法的作用是判断当前的uri是否用户最后提交的uti(isViewCollected() || isViewReused())。

  2. tryLoadBitmap()方法

tryLoadBitmap()的代码如下所示:

private Bitmap tryLoadBitmap() throws TaskCancelledException {
    Bitmap bitmap = null;
    try {
        File imageFile = configuration.diskCache.get(uri); //是否命中磁盘缓存(DiskCache类)
        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())); //由Decoder来进行decode
        }
        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()) { //尝试从网络下载图片,并将其保存到disk上
                imageFile = configuration.diskCache.get(uri); // 再次从disk读入图片
                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); //在engine或指定handler上执行onLoadingFailed回调
            }
        }
    } 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;
}

代码执行流程方面的解析见中文注释,留意其中的tryCacheImageOnDisk()方法:

private boolean tryCacheImageOnDisk() throws TaskCancelledException {
    L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);

    boolean loaded;
    try {
        loaded = downloadImage(); //下载image
        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); // 将image保存到disk
            }
        }
    } catch (IOException e) {
        L.e(e);
        loaded = false;
    }
    return loaded;
}

private boolean downloadImage() throws IOException {
    InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader()); // 调用Downloader下载bitmap
    if (is == null) {
        L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
        return false;
    } else {
        try {
            return configuration.diskCache.save(uri, is, this); // 将bitmap直接保存到DiskCache
        } finally {
            IoUtils.closeSilently(is);
        }
    }
}

private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
    // Decode image file, compress and re-save it
    boolean saved = false;
    File targetFile = configuration.diskCache.get(uri);
    if (targetFile != null && targetFile.exists()) {
        ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
        DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
                .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build(); // cloneFrom方法在已有的options的基础上添加其他option
        ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
                Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
                getDownloader(), specialOptions);
        Bitmap bmp = decoder.decode(decodingInfo); // Decoder根据targetImageSize,从File中解析出Bitmap
        if (bmp != null && configuration.processorForDiskCache != null) {
            L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
            bmp = configuration.processorForDiskCache.process(bmp); // 保存到disk前对bitmap进行处理
            if (bmp == null) {
                L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
            }
        }
        if (bmp != null) {
            saved = configuration.diskCache.save(uri, bmp); // 将处理后的Bitmap保存到DiskCache
            bmp.recycle();
        }
    }
    return saved;
}

代码执行流程方面的解析见中文注释。值得注意的是,为什么要多次先写bitmap到文件再从文件将其读出来再使用?贴一张官网的流程图帮助大家思考:

缓存命中策略

至此,displayImage的流程简析完成。下面看一下第二个用例。

loadImage

用例的代码如下,作用是仅使用imageLoader的缓存和下载功能,显示部分由用户负责。

// Load image, decode it to Bitmap and return Bitmap to callback
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        // Do whatever you want with Bitmap
    }
});

最终会调用:

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方法,但是传入了一个NonViewAware类。NonViewAware不包含ImageView对象,setImageDrawable和setImageBitmap方法都是空实现。

loadImageSync

用例代码如下所示:

// Load image, decode it to Bitmap and return Bitmap synchronously
Bitmap bmp = imageLoader.loadImageSync(imageUri);

最终调用如下所示:

public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) {
    if (options == null) {
        options = configuration.defaultDisplayImageOptions;
    }
    options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build();// cloneFrom方法在已有的options的基础上添加syncLoading特性

    SyncImageLoadingListener listener = new SyncImageLoadingListener();
    loadImage(uri, targetImageSize, options, listener);
    return listener.getLoadedBitmap();
}

可以看出loadImageSync()是通过调用loadImage()来实现的。注意其传入参数SyncImageLoadingListener对象。SyncImageLoadingListener实现了SimpleImageLoadingListener接口,在onLoadingComplete()回调中将loadedImage保存下来,其后可通过getLoadedBitmap方法取出。

画框图

首先整理出上述代码中的关键类,然后大致画出它们之间的调用关系,如下所示:

 +----------------------------+
 |ImageDownloader             |
 | +------------------------+ |
 | | ImageLoaderEngine      | |
 | |   +------------------+ | |
 | |   |Deque             | | |
 | |   | +----+ +----+    | | |
 | |   | |Task| |Task| ...| | |
 | |   | +----+ +----+    | | |
 | |   +------------------+ | |
 | +------------------------+ |
 | +-------------+            |
 | |configuration|            |
 | +-------------+            |
 +----------------------------+
   +-------+
   | Utils |
   +-------+

+----------------------------------------------------+
|a running task                                      |
|                   +                                |
|                   |                                |
|              +----v----+        +---------+        |
|              |mem cache+-------->Processor|        |
|              +----+--^-+        +--+------+        |
|                   |  |             |               |
|                   |  |             |               |
|              +----v--+--+          |  +---------+  |
|     +-------->disk cache+-----+    +-->Displayer|  |
|     |        +----+--^--+     |       +---------+  |
| +---+-----+       |  |        |                    |
| |Processor|       |  |        |                    |
| +---^-----+    +--v--+-+      |                    |
|     +----------+Decoder|      |                    |
|                +-----^-+      |                    |
|                      |        |                    |
|                      |     +--v-------+            |
|                      +-----+Downloader|            |
|                            +----------+            |
|                                                    |
+----------------------------------------------------+

本文只是对UIL的结构做了简单的解析,等到用的时候踩坑了再深入了解吧。


legendmohe
639 声望29 粉丝

求职