1. 程式人生 > >Universal-Image-Loader的圖片載入流程原始碼分析

Universal-Image-Loader的圖片載入流程原始碼分析

概述

Universal-Image-Loader是經典的圖片載入框架,雖然現在該專案不再維護,但對於初學者依舊是值得學習的開源專案之一,本文就該框架的載入圖片流程做簡要梳理,希望讀者有所收穫。
該文參考了【codeKK】 Android Universal Image Loader 原始碼分析一文,該文詳細分析了Universal-Image-Loader的設計思想,想深入瞭解,可以祥讀此文。

基本工作流程

首先來看看作者給出的工作流程圖:

上圖基本的載入流程是當圖片請求發出時,首先會從記憶體快取中查詢該bitmap是否存在,如果存在,則由BitmapProcessor這個類來進行處理,然後由DisplayDisplaer

這個類來進行顯示;
如果記憶體快取不存在該圖片,那麼會從硬碟快取查詢,如果存在,則由ImageDecoder類來將圖片解析為Bitmap,然後由BitmapProcessor來進行圖片處理,然後由MemoryCache進行記憶體快取,方便下次查詢,之後再交由BitmapProcessorDisplayDisplaer來進行處理和最終顯示;
如果記憶體快取和硬碟快取都沒有找到該圖片,那麼將由ImageDownloader來下載圖片,然後將該圖片由DiskCache來進行硬碟快取,快取好後,之後的流程就和上面的流程一致了。

使用方法

瞭解了這個基本流程,我們看看Universal-Image-Loader

的基本使用方法:

首先使用時候需要初始化設定:

 
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);

ImageLoader.getInstance().init(configuration);
傳遞的`this`引數為`Context`型別,因此一般會在自定義的`Application`中的`onCreate`方法中進行初始化:
 
public class BaseApplication extends Application{

    private static Context mAppContext;    

    @Override    
    public void onCreate() { 
    super.onCreate(); 
    //用於初始化預設配置,會配置一些預設引數
    ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
    //將上面初始化的配置傳遞給ImageLoader  
    ImageLoader.getInstance().init(configuration);    
  }

}

在正式載入圖片之前,我們還可能需要做一些配置:

public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.bj_weixianshi)//載入中的等待圖片
            .displayer(new SimpleBitmapDisplayer())//選擇的顯示類
            .showImageOnFail(R.drawable.bj_weixianshi)//顯示失敗載入的圖片
            .cacheInMemory(true)//開啟記憶體快取
            .cacheOnDisk(true)//開啟硬碟快取
            .bitmapConfig(Bitmap.Config.RGB_565)//圖片顯示模式
            .build();

好了,這樣就可以呼叫真正的載入方法進行展示了:

ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );

方法很簡單,第一個引數就是圖片URI,支援型別:

"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)

可以看到支援本地和網路的圖片uri;
第二個引數就是ImageView,第三個引數就是上面的simpleOptions,通過這三個引數,就能完成圖片的顯示。

流程分析

首先我們來這個這行程式碼:
ImageLoader.getInstance().init(configuration);
看起來是個單例模式,我們跟蹤原始碼看看:

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

很典型的單例模式,保證全域性只有一個ImageLoader,不必重複建立物件,節省記憶體。
再來看看配置:

public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.bj_weixianshi)//載入中的等待圖片
             ...省略
            .build();

因為可選引數較多,這裡用了建造者模式。
好了,看完上面,分析下載入圖片的流程:
ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );
該方法最終會呼叫到這個方法:

public void displayImage(String uri, //圖片的uri,可能來自本地或者網路
                             ImageAware imageAware,//該介面型別主要封裝了一些獲取View控制元件寬高等的常見方法,實現類之一ImageViewAware
                             DisplayImageOptions options,//展示圖片的一些可選項封裝類,如是否快取,同步非同步等
                             ImageSize targetSize,//圖片顯示的最終尺寸,封裝處理類,主要是給圖片一個合理的顯示尺寸
                             ImageLoadingListener listener,//圖片載入監聽,載入開始,載入失敗,載入完成,載入取消幾種狀態
                             ImageLoadingProgressListener progressListener//圖片載入中的監聽,可用於顯示載入進度條
    ) {
        checkConfiguration();//檢查配置是否為null,為Null報異常
        if (imageAware == null) {//檢查配置是否為null,為Null報異常
            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
        }
        if (listener == null) {//檢查載入監聽,如果為Null,就載入defaultListener
            listener = defaultListener;
        }
        if (options == null) {//載入配置選項,為null,則載入預設的options
            options = configuration.defaultDisplayImageOptions;
        }

        if (TextUtils.isEmpty(uri)) {//檢查圖片uri是否為空
            engine.cancelDisplayTaskFor(imageAware);//空的情況下,取消載入任務
            listener.onLoadingStarted(uri, imageAware.getWrappedView());//呼叫監聽方法
            if (options.shouldShowImageForEmptyUri()) {//如果配置了空uri情況下的圖片顯示,那麼載入預設圖片
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {//否則顯示Null
                imageAware.setImageDrawable(null);
            }
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//載入完成,呼叫監聽
            return;
        }

        if (targetSize == null) {//檢查圖片大小配置,如果為Null,生成預設的圖片大小
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);//根據uri和targetSize來生成記憶體快取的Key
        //將要顯示的View和key加入一個執行緒同步的快取Map,key為view的id,value為快取memoryCacheKey,該map用於判斷載入任務是否重複
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

        listener.onLoadingStarted(uri, imageAware.getWrappedView());//呼叫監聽方法

        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根據Key從記憶體快取查詢是否有快取的bitmap
        if (bmp != null && !bmp.isRecycled()) {//檢查快取的bitmap是否為null,或是否被回收
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

            if (options.shouldPostProcess()) {//是否進行bitmap處理
                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將進行相關處理,並最終顯示圖片
                    displayTask.run();
                } else {//非同步載入,提交displayTask到任務佇列,再進行處理顯示
                    engine.submit(displayTask);
                }
            } else {//不進行bitmap處理,直接顯示圖片
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);//載入完成,呼叫監聽
            }
        } else {//如果記憶體中沒有快取的bitmap,則根據uri從本地快取或網路載入
            if (options.shouldShowImageOnLoading()) {//如果設定了載入中的圖片,則進行顯示
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {//如果設定了載入前重置圖片,那麼給圖片設定Null
                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立即執行
                displayTask.run();
            } else {//否則提交displayTask到任務佇列再執行
                engine.submit(displayTask);
            }
        }
    }

原始碼中進行了簡單註釋,整個載入流程就在這一個方法中。
我們將其中幾個重要步驟分解來看:
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根據Key從記憶體快取查詢是否有快取的bitmap
從記憶體快取中查詢快取的bitmap,我們看看memoryCache的具體實現:
final MemoryCache memoryCache;ImageLoaderConfiguration的成員變數,看看它在哪裡初始化的:

        public ImageLoaderConfiguration build() {
            initEmptyFieldsWithDefaultValues();
            return new ImageLoaderConfiguration(this);
        }

        private void initEmptyFieldsWithDefaultValues() {
            ...省略
            if (memoryCache == null) {//如果沒有配置,則建立預設的記憶體快取
                memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
            }
            ...省略
        }

我們再看看這個方法:

        public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
        if (memoryCacheSize == 0) {
            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            int memoryClass = am.getMemoryClass();
            if (hasHoneycomb() && isLargeHeap(context)) {//如果SDK版本為3.0以上,並配置了android:largeHeap="true",可以申請大記憶體
                memoryClass = getLargeMemoryClass(am);
            }
            memoryCacheSize = 1024 * 1024 * memoryClass / 8;//  1/8可用記憶體
        }
        return new LruMemoryCache(memoryCacheSize);//預設實現為LruMemoryCache
    }

LruMemoryCache為介面MemoryCache的實現類,作者為我們提供了多種MemoryCache的實現類,來適應不用的記憶體快取需求:

MemoryCache的多種實現.png
有興趣的同學可以分析下這些快取實現,本文只分析LruMemoryCache,其實該類個人理解是簡化版的LruCache,看看原始碼:

/**
 * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
 * the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
 * become eligible for garbage collection.<br />
 * <br />
 * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.8.1
 *
 * 快取了一定數量的Bitmap的強引用。當一個Bitmap被訪問時,它會移動到序列的隊尾。當快取滿時,再新增Bitmap,會將
 * 序列頭部的Bitmap釋放掉,等待GC回收。
 *
 */
public class LruMemoryCache implements MemoryCache {

    //內部使用LinkedHashMap來儲存Bitmap
    private final LinkedHashMap<String, Bitmap> map;
    //最大快取的位元組數
    private final int maxSize;
    /** Size of this cache in bytes */
    private int size;//當前快取的位元組數

    /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
    public LruMemoryCache(int maxSize) {
        if (maxSize <= 0) {//檢查設定的快取大小,小於0丟擲異常
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //建立一個基於訪問順序的LinkedHashMap,設定false就是預設的插入順序,這裡不討論LinkedHashMap的實現了
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
    }

    /**
     * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
     * of the queue. This returns null if a Bitmap is not cached.
     * 根據key獲取快取的Bitmap,如果能夠獲取到,那麼該引用會移動到佇列尾部。如果沒有快取就返回Null
     */
    @Override
    public final Bitmap get(String key) {
        if (key == null) {//檢查key是否為null,是null拋異常
            throw new NullPointerException("key == null");
        }
        //同步,多執行緒訪問時保證執行緒安全
        synchronized (this) {
            return map.get(key);
        }
    }

    /** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue.
     *  put進的bitmap會放進隊尾
     */

    @Override
    public final boolean put(String key, Bitmap value) {
        if (key == null || value == null) {//檢查key和value值,如果為null丟擲異常
            throw new NullPointerException("key == null || value == null");
        }

        synchronized (this) {//開啟同步
            size += sizeOf(key, value);//累計計算bitmap的大小
            Bitmap previous = map.put(key, value);//加入map
            if (previous != null) {//如果之前存在該快取的bitmap,那麼size就不再累計該bitmap的大小
                size -= sizeOf(key, previous);//
            }
        }
        //檢查size是否在合理的範圍內,如果不再做相應處理
        trimToSize(maxSize);
        return true;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
     *
     * 該方法主要控制快取的bitmap不超過maxSize,一旦超過就移除最久沒用用過的bitmap,直到小於maxSize
     */
    private void trimToSize(int maxSize) {
        while (true) {//開啟迴圈
            String key;
            Bitmap value;
            synchronized (this) {//開啟同步
                if (size < 0 || (map.isEmpty() && size != 0)) {//檢查size或map是否正常,否則丟擲異常
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }
                //如果size小於maxSize或者map是空的,那麼說明map狀態正常,結束迴圈
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                //查詢佇列頭部儲存的bitmap
                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {//如果要移除的entry物件為null,那麼也結束迴圈
                    break;
                }
                //獲取key,和value
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除對應的bitmap
                size -= sizeOf(key, value);//重新計算size大小
            }
        }
    }

    /** Removes the entry for {@code key} if it exists.
     *  根據Key移除對應的bitmap
     */
    @Override
    public final Bitmap remove(String key) {
        if (key == null) {//檢查key是否為null,null丟擲異常
            throw new NullPointerException("key == null");
        }

        synchronized (this) {//開啟執行緒同步
            Bitmap previous = map.remove(key);//移除
            if (previous != null) {//如果之前的bitmap存在,那麼重新計算size大小
                size -= sizeOf(key, previous);
            }
            return previous;
        }
    }

    @Override
    public Collection<String> keys() {//獲取key的set集合
        synchronized (this) {
            return new HashSet<String>(map.keySet());
        }
    }

    @Override//清空map
    public void clear() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * Returns the size {@code Bitmap} in bytes.
     * <p/>
     * An entry's size must not change while it is in the cache.
     * 計算size方法
     */
    private int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    public synchronized final String toString() {
        return String.format("LruCache[maxSize=%d]", maxSize);
    }
}

設計還是比較清晰簡單的,主要是通過LinkedHashMap來進行儲存。
如果記憶體快取中獲取不到bitmap,那麼將根據uri從本地或網路進行載入,所有的資訊都封裝在LoadAndDisplayImageTask類中,該類實現了Runnable介面,因此整個過程是在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();//判斷當前任務是否正常,view是否被回收,任務是否正常
            //再次從記憶體中獲取快取的bitmap,個人理解是當請求任務很多時,很可能之前的執行緒,已經將圖片快取了,所以再次獲取
            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 {//如果存在快取,那麼做標記,說明是記憶體快取
                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);//顯示圖片
    }

這裡看下bmp = configuration.memoryCache.get(memoryCacheKey);,如果if (bmp == null || bmp.isRecycled())那麼就會走到bmp = tryLoadBitmap();,我們看看這個方法:

    /**
     * 獲取圖片
     * @return
     * @throws TaskCancelledException
     */
    private Bitmap tryLoadBitmap() throws TaskCancelledException {
        Bitmap bitmap = null;
        try {//根據uri從本地快取獲取圖片的快取檔案
            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()));
            }
            //判斷是否bitmap是否存在,如果不存在,那麼將從網路進行載入
            if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
                loadedFrom = LoadedFrom.NETWORK;//做網路標記

                String imageUriForDecoding = uri;
                //如果設定了本地快取開啟,tryCacheImageOnDisk()方法會去網路獲取圖片,並快取到本地
                if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                    imageFile = configuration.diskCache.get(uri);
                    if (imageFile != null) {
                        imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                    }
                }

                checkTaskNotActual();//檢測任務是否正常
                //如果設定了本地快取開啟,那麼bitmap會從本地載入,如果沒有則從網路載入
                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;
    }

通過上面的方法分析我們可以大致知道如果開啟了本地快取,那麼將會執行tryCacheImageOnDisk()先下載圖片再本地快取,而如果沒用開啟快取,那麼會執行decodeImage(imageUriForDecoding)去網路下載圖片,這裡我們分析下通過本地快取和網路載入兩種方式,主要了解下兩者的實現區別,看看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);
                   // 如果設定了本地圖片快取的最大寬高,預設為0,重新設定圖片大小並再次本地快取
                    resizeAndSaveImage(width, height); 
                }
            }
        } catch (IOException e) {
            L.e(e);
            loaded = false;
        }
        return loaded;
    }

看看downloadImage()方法:

private boolean downloadImage() throws IOException {
        //根據uri來獲取流
        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
        if (is == null) {
            L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
            return false;
        } else {
            try {//進行本地快取
                return configuration.diskCache.save(uri, is, this);
            } finally {
                IoUtils.closeSilently(is);
            }
        }
    }

getStream()方法預設由BaseImageDownloader物件實現:

public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            //根據不同字首來選擇不同的圖片載入方式
            case HTTP:
            case HTTPS://網路
                return getStreamFromNetwork(imageUri, extra);
            case FILE://檔案
                return getStreamFromFile(imageUri, extra);
            case CONTENT://content provider
                return getStreamFromContent(imageUri, extra);
            case ASSETS://assets 目錄
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE://圖片
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

根據uri的字首來選擇不同的載入方式,這裡我們看看從getStreamFromNetwork(imageUri, extra)`方法:

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        HttpURLConnection conn = createConnection(imageUri, extra);//建立一個HttpURLConnection物件
        int redirectCount = 0;//重定向次數
        //最大5次重定向
        while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }

        InputStream imageStream;
        try {//獲取圖片流
            imageStream = conn.getInputStream();
        } catch (IOException e) {
            // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
            IoUtils.readAndCloseStream(conn.getErrorStream());
            throw e;
        }
        if (!shouldBeProcessed(conn)) {//如果狀態碼不是200,那麼關閉流
            IoUtils.closeSilently(imageStream);
            throw new IOException("Image request failed with response code " + conn.getResponseCode());
        }
        //重新封裝流
        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
    }

至此,就從網路獲取到圖片流了,然後轉換為bitmap,然後進行本地快取,如果設定了diskCacheSize或者diskCacheFileCount,只要滿足任意一個條件,那麼就會用LruDiskCache實現,否者用UnlimitedDiskCache實現,先看看UnlimitedDiskCache吧,其實該類UnlimitedDiskCache,就是BaseDiskCache,這個對本地快取沒有大小限制,所以看看BaseDiskCache的實現:

//根據圖片uri,來儲存uri
    @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 {//將bitmap儲存到臨時檔案
            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;
    }


/** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
    //根據uri獲取快取圖片檔案
    protected File getFile(String imageUri) {
        String fileName = fileNameGenerator.generate(imageUri);//生成檔名
        File dir = cacheDir;//快取目錄
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {//如果快取目錄不存在,那麼就用備用快取目錄
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        return new File(dir, fileName);
    }

主要就是這兩個方法了,還是比較簡單。
至於LruDiskCache類的原始碼,內部其實是由JakeWhartonDiskLruCache實現的,所以單獨另開一篇分析此類,本文分析到此為止。