Universal-Image-Loader源碼分析(二)——載入圖片的過程分析

以前的文章,在上面創建完config以後,UIl經過ImageLoader.getInstance().init(config.build());來初始化ImageLoader對象,以後就能夠用ImageLoader來加載圖片。java

這裏,採用到單例模式來獲取ImageLoader對象,保證他全局初始化一次。再上面的分析中,咱們能夠看出單例模式的好處,建立ImageLoader對象的時候須要建立Config,而Config裏面一樣初始化了一堆對象。若是每次用到都現初始化ImageLoader,消耗太大。咱們看一下ImageLoader的init的源碼android

public synchronized void init(ImageLoaderConfiguration configuration) {
        if (configuration == null) {
            throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
        }
        if (this.configuration == null) {
            L.d(LOG_INIT_CONFIG);
            //建立圖片加載引擎
            engine = new ImageLoaderEngine(configuration);
            this.configuration = configuration;
        } else {
            L.w(WARNING_RE_INIT_CONFIG);
        }
    }

若是該對象尚未config,則將傳入的config賦值給this.config。而且初始化圖片加載引擎。緩存

咱們繼續看圖片加載引擎主要執行的工做。網絡

class ImageLoaderEngine {
    /*ImageLoader加載配置*/
    final ImageLoaderConfiguration configuration;
    /*任務執行者*/
    private Executor taskExecutor;
    /*圖片緩存任務執行則*/
    private Executor taskExecutorForCachedImages;
    /*任務分配者*/
    private Executor taskDistributor;

    private final Map<Integer, String> cacheKeysForImageAwares = Collections
            .synchronizedMap(new HashMap<Integer, String>());
    private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
    /*暫停*/
    private final AtomicBoolean paused = new AtomicBoolean(false);
    /*網絡拒絕訪問*/
    private final AtomicBoolean networkDenied = new AtomicBoolean(false);
    /*網絡慢*/
    private final AtomicBoolean slowNetwork = new AtomicBoolean(false);

    private final Object pauseLock = new Object();

    /**
     * ImageLoader引擎構造器
     * @param configuration
     */
    ImageLoaderEngine(ImageLoaderConfiguration configuration) {
        //初始化ImageLoader配置參數
        this.configuration = configuration;
        //初始化三個不一樣任務的執行者
        taskExecutor = configuration.taskExecutor;
        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
    }

    /** Submits task to execution pool */
    /**
     * 提交圖片加載和顯示任務到執行線程池中,進行運行
     * @param task   具體須要執行的任務
     */
    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來進行執行任務
                    taskExecutorForCachedImages.execute(task);
                } else {
                    //當天圖片在本地文件系統中沒有緩存,直接採用taskExecutor來進行執行任務
                    taskExecutor.execute(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();
        }
    }

    /**
     * 進行建立任務執行者
     * @return
     */
    private Executor createTaskExecutor() {
        return DefaultConfigurationFactory
                .createExecutor(configuration.threadPoolSize, configuration.threadPriority,
                configuration.tasksProcessingType);
    }

    /**
     * 獲取當前被加載ImageAware到圖片的地址
     * Returns URI of image which is loading at this moment into passed {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}
     */
    String getLoadingUriForView(ImageAware imageAware) {
        return cacheKeysForImageAwares.get(imageAware.getId());
    }

    /**
     *
     * Associates <b>memoryCacheKey</b> with <b>imageAware</b>. Then it helps to define image URI is loaded into View at
     * exact moment.
     */
    void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
        cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
    }

    /**
     * Cancels the task of loading and displaying image for incoming <b>imageAware</b>.
     *
     * @param imageAware {@link com.nostra13.universalimageloader.core.imageaware.ImageAware} for which display task
     *                   will be cancelled
     */
    void cancelDisplayTaskFor(ImageAware imageAware) {
        cacheKeysForImageAwares.remove(imageAware.getId());
    }

    /**
     * Denies or allows engine to download images from the network.<br /> <br /> If downloads are denied and if image
     * isn't cached then {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired
     * with {@link FailReason.FailType#NETWORK_DENIED}
     *
     * @param denyNetworkDownloads pass <b>true</b> - to deny engine to download images from the network; <b>false</b> -
     *                             to allow engine to download images from network.
     */
    void denyNetworkDownloads(boolean denyNetworkDownloads) {
        networkDenied.set(denyNetworkDownloads);
    }

    /**
     * Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle <a
     * href="http://code.google.com/p/android/issues/detail?id=6066">this known problem</a> or not.
     *
     * @param handleSlowNetwork pass <b>true</b> - to use {@link FlushedInputStream} for network downloads; <b>false</b>
     *                          - otherwise.
     */
    void handleSlowNetwork(boolean handleSlowNetwork) {
        slowNetwork.set(handleSlowNetwork);
    }

    /**
     * Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.<br
     * /> Already running tasks are not paused.
     * 暫停任務運行
     */
    void pause() {
        paused.set(true);
    }

    /**
     * Resumes engine work. Paused "load&display" tasks will continue its work.
     * 任務恢復運行
     */
    void resume() {
        paused.set(false);
        synchronized (pauseLock) {
            pauseLock.notifyAll();
        }
    }

    /**
     * 中止ImageLoader引擎,取消全部正在運行或者掛起的圖片顯示任務,而且清除內部的數據
     * Stops engine, cancels all running and scheduled display image tasks. Clears internal data.
     * <br />
     * <b>NOTE:</b> This method doesn't shutdown
     * {@linkplain com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.Executor)
     * custom task executors} if you set them.
     */
    void stop() {
        if (!configuration.customExecutor) {
            ((ExecutorService) taskExecutor).shutdownNow();
        }
        if (!configuration.customExecutorForCachedImages) {
            ((ExecutorService) taskExecutorForCachedImages).shutdownNow();
        }
        cacheKeysForImageAwares.clear();
        uriLocks.clear();
    }

    void fireCallback(Runnable r) {
        taskDistributor.execute(r);
    }

    ReentrantLock getLockForUri(String uri) {
        ReentrantLock lock = uriLocks.get(uri);
        if (lock == null) {
            lock = new ReentrantLock();
            uriLocks.put(uri, lock);
        }
        return lock;
    }

    AtomicBoolean getPause() {
        return paused;
    }

    Object getPauseLock() {
        return pauseLock;
    }

    boolean isNetworkDenied() {
        return networkDenied.get();
    }

    boolean isSlowNetwork() {
        return slowNetwork.get();
    }
}

上面的代碼中,核心的部分就是建立了任務執行器和圖片緩存執行器,而且在submit方法中針對提交的任務,選擇不一樣的執行器執行。app

ImageLoader關於加載顯示圖片,有以下幾種用法,咱們依次分析一下。框架

displayImage(), loadImage()

先看loadImage()ide

下面的loadImage全部的重載方法。post

public void loadImage(String uri, ImageLoadingListener listener) {
        loadImage(uri, null, null, listener, null);
    }
    
    public void loadImage(String uri, ImageSize targetImageSize, ImageLoadingListener listener) {
        loadImage(uri, targetImageSize, null, listener, null);
    }
    
    public void loadImage(String uri, DisplayImageOptions options, ImageLoadingListener listener) {
        loadImage(uri, null, options, listener, null);
    }
    
    public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
            ImageLoadingListener listener) {
        loadImage(uri, targetImageSize, options, listener, null);
    }
    public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
    }

不論是幾個參數的loadImage,最後都會重載下面的方法。ui

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

若是沒有指定targetImageSize以及options就會採用config默認的提供,而後根據targetImageSize以及uri生成一個NonViewAware,最終經過displayImage來加載。this

targetImageSize是一個ImageSize對象,該類是image尺寸的封裝類,config的默認值是屏幕的寬高。
options是採用config默認建立,config的默認值是faultDisplayImageOptions = DisplayImageOptions.createSimple();
這個初始化都是建立的DisplayImageOptions都是默認值,基本什麼屬性都是false或者null或者0

接下來,咱們看一下NonViewAware類,NonViewAware是實現了ImageAware接口的一個類,ImageAware接口主要定義了圖片處理和顯示所須要的方法和屬性。因此NonViewAware也只是對傳入的參數進行封裝,來提供一個外部訪問的接口。

真正顯示圖片的方法都是displayImage,displayImage方法和loadImage同樣,提供了多種參數,displayImage的重載方法要多一些,由於displayImage方法有一類是接受ImageView而另外一類是接受ImageAware。
下面是不管如何都是最終調用的方法:
代碼以下:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        //進行檢查ImageLoader全局相關配置
        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)) {
            engine.cancelDisplayTaskFor(imageAware);
            //接口方法回調,當前圖片加載任務開始
            listener.onLoadingStarted(uri, imageAware.getWrappedView());
            //進行判斷是否給imageview添加一個空地址的資源圖片
            if (options.shouldShowImageForEmptyUri()) {
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {
                imageAware.setImageDrawable(null);
            }
            //直接加載回調加載成功
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
            return;
        }
        //=============圖片地址存在=====================
        if (targetSize == null) {
            //若是圖片顯示的目標大小沒有設置的,那麼就使用默認大小尺寸便可
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        //根據地址和圖片目標尺寸信息,生成緩存key
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
        //開始進行加載圖片
        listener.onLoadingStarted(uri, imageAware.getWrappedView());

        //首先根據key去緩存中獲取是否還存在該圖片
        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()) {
                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);
            }
        }
    }

具體的執行過程已經經過代碼註釋了,下面梳理下流程。
首先,針對爲null的屬性,初始化這些屬性,而後判斷傳入的uri是否是爲空,若是爲空,則根據options.shouldShowImageForEmptyUri來判斷是否顯示先設置好的圖片。
若是傳入的uri是存在的,則根據地址和圖片目標尺寸的信息來生成緩存key,而後執行
prepareDisplayTaskFor方法。
再根據key來判斷緩存中是否存在該圖片,若是存在,就判斷options的shouldPostProcess,若是爲true就建立一個ProcessAndDisplayImageTask對象,經過圖片加載引擎來加載。若是返回的值false,則調用options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);

若是緩存不存在,則直接經過網絡加載該圖片,加載的方式是構建LoadAndDisplayImageTask對象,經過圖片加載引擎來判斷。

在這裏,關於圖片的緩存相關的內容,先不分析。接下來主要分析的是圖片如何執行這兩種不一樣的任務的。

第一種,在有緩存的狀況下,經過判斷shouldPostProcess爲true來讓圖片引擎處理任務,這裏的shouldPostProcess是指在拿到bitmap以後是否進行後續的操做,判斷標準就是postProcess是否爲null.
若是不爲null,也就是shouldPostProcess爲true,則執行下面的代碼:
下面,咱們來看一下,圖片引擎是如何執行ProcessAndDisplayImageTask。

void submit(ProcessAndDisplayImageTask task) {
        initExecutorsIfNeed();
        taskExecutorForCachedImages.execute(task);
    }

內部直接調用taskExecutorForCachedImages去執行task,因此主要看ProcessAndDisplayImageTask的task構造。

final class ProcessAndDisplayImageTask implements Runnable {

    private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
    /*ImageLoader引擎*/
    private final ImageLoaderEngine engine;
    private final Bitmap bitmap;
    /*ImageLoader信息封裝對象*/
    private final ImageLoadingInfo imageLoadingInfo;
    private final Handler handler;

    /**
     * 圖片處理顯示任務構造器
     * @param engine
     * @param bitmap
     * @param imageLoadingInfo
     * @param handler
     */
    public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, ImageLoadingInfo imageLoadingInfo,
            Handler handler) {
        this.engine = engine;
        this.bitmap = bitmap;
        this.imageLoadingInfo = imageLoadingInfo;
        this.handler = 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);
    }
}

這邊能夠看出,task內部run方法裏面,首先獲得一個BitmapProcessor,而後經過該processor去處理bitmap,而後將處理後的bitmap以及其餘信息封裝成了DisplayBitmapTask,而後最終仍是執行了LoadAndDisplayImageTask的runTask方法

下面將看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分發到主線程執行
            handler.post(r);
        }
    }

這邊,直接在UI線程displayBitmapTask,後面再看displayBitmapTask的內部實現。
回到shouldPostProcess的判斷那裏,若是爲false,則直接調用BitmapDisplay顯示圖片,這裏傳入的是SimpleBitmapDisplayer
再回到緩存判斷那裏,上面的代碼都是在有內存緩存的狀況下,執行的。看一下在無內存緩存時,執行的細節。

//緩存中不存在該圖片 經過網絡加載
            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);
            }

經過源碼能夠看出,首先判斷要不要進行顯示加載中的View,而後構建圖片加載信息,經過圖片加載信息構建LoadAndDisplayImageTask對象,執行去run方法。
上面,咱們已經分析了其runTask方法,該方法比較簡單,此次咱們看一下run方法。

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 {
                //從緩存中獲取到圖片信息
                //設置圖片來源信息 --Memory Cache
                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);
    }

其流程圖以下:

能夠看到這邊,利用的圖片三級緩存,第一級是內存緩存,若是內存緩存沒有則利用二級緩存,從文件中去讀取,若是文件中有,則從文件中取出bitmap,若是沒有則從網絡下載。
這邊從文件中bitmap的方法是tryLoadBitmap,下面主要看一下這個方法

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對象
                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;
    }

首先是從diskcache中,取出File,而後對file進行轉換。若是轉換後的bitmap爲null,則從網絡獲取圖片,獲取圖片的方法調用是在options.isCacheOnDisk() && tryCacheImageOnDisk() 該判斷首先判斷是否存儲在文件中,若是不存在文件中,就不執行後面的網絡獲取。

private boolean tryCacheImageOnDisk() throws TaskCancelledException {
        L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
        boolean loaded;
        try {
            //圖片下載而且保存本地
            loaded = downloadImage();
            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); // TODO : process boolean result
                }
            }
        } catch (IOException e) {
            L.e(e);
            loaded = false;
        }
        return loaded;
    }

downloadImage是執行網絡請求的方法,其內部經過BaseImageDownloader進行下載,其內部的網絡庫是HttpURLConnection,下載後的圖片,根據設定的文件存儲的最大寬高,進行縮放與保存。
這樣,就在文件緩存中緩存了圖片,在回到上面LoadAndDisplayTask的run方法,在獲得bitmap以後,就會判斷是否要 對bitmap進行預處理,預處理完的bitmap所有會緩存到內存緩存中。上面的操做都是創建在bitmap中內存緩存中取沒有取出來的狀況,若是取出來就直接獲得bitmap,而後從bitmap判斷是否進行後續的處理。

這裏,簡單說一下 preProcessor以及postProcessor,preProcessor是指對圖片進行預處理,好比加水印,若是加水印的圖片都會緩存到內存,postProcessor是對取出的bitmap作一些後續的操做,操做後將顯示出來。
最後獲得的Bitmap會封裝成DisplayBitmapTask,調用上面提到的runtask方法,進行處理。

這樣,到此,發起圖片獲取需求,到圖片通過內存緩存,文件緩存,網絡獲取後獲得,而後再經過Handler回到UI線程的流程就分析完畢了。
其實,整個流程很是的簡單,清晰。只不過在考慮到了多種狀況,使得代碼看上去不少。

下面,將分析圖片加載框架最重要的一部分,緩存的設計。

相關文章
相關標籤/搜索