Universal Image Loader源碼淺析

本文僅對UIL中一些簡單的用例作解析 java

畫出源碼目錄樹

首先,用腳本生成了該項目源碼的目錄樹,而後大體瀏覽一下文件內容,猜想其做用:git

-[ 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入手,推斷其項目架構

官網上給出的最簡單的使用例子以下所示:github

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);這一句。其源碼以下所示:app

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

一路調用到以下所示方法:ide

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的結構作了簡單的解析,等到用的時候踩坑了再深刻了解吧。

相關文章
相關標籤/搜索