天天看點

XUtils 源碼分析(二)--圖檔緩存子產品一 XUtils圖檔緩存子產品源碼分析二 緻敬原作者,謝謝作者辛苦付出,代碼受益匪淺。三 其他講解:四 源碼注釋

一 XUtils圖檔緩存子產品源碼分析

1.BitmapUtils對象建立

bitmapUtils = new BitmapUtils(appContext);



public BitmapUtils(Context context) {

    this(context, null);

}



public BitmapUtils(Context context, String diskCachePath) {

    if (context == null) {

        throw new IllegalArgumentException("context may not be null");

    }



    this.context = context.getApplicationContext();

    globalConfig = BitmapGlobalConfig.getInstance(this.context, diskCachePath);

    defaultDisplayConfig = new BitmapDisplayConfig();

}



public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize) {

    this(context, diskCachePath);

    globalConfig.setMemoryCacheSize(memoryCacheSize);//記憶體緩存大小

}



public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize, int

        diskCacheSize) {

    this(context, diskCachePath);

    globalConfig.setMemoryCacheSize(memoryCacheSize);

    globalConfig.setDiskCacheSize(diskCacheSize);//磁盤緩存大小

}



public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent) {

    this(context, diskCachePath);

    globalConfig.setMemCacheSizePercent(memoryCachePercent);

}



public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent, int

        diskCacheSize) {

    this(context, diskCachePath);

    globalConfig.setMemCacheSizePercent(memoryCachePercent);

    globalConfig.setDiskCacheSize(diskCacheSize);

}
           

1.1 擷取BitmapGlobalConfig對象BitmapGlobalConfig.getInstance(this.context, diskCachePath);

public synchronized static BitmapGlobalConfig getInstance(Context context,

                                                          String diskCachePath) {

    if (TextUtils.isEmpty(diskCachePath)) {

        diskCachePath = OtherUtils.getDiskCacheDir(context, "xBitmapCache");//緩存路徑

    }

    if (configMap.containsKey(diskCachePath)) {//有緩存的對象

        return configMap.get(diskCachePath);

    } else {

        BitmapGlobalConfig config = new BitmapGlobalConfig(context, diskCachePath);

        configMap.put(diskCachePath, config);

        return config;

    }

}





/**

 * @param context

 * @param dirName Only the folder name, not full path.

 * @return app_cache_path/dirName

 * 1.檢查外部存儲如果挂載去擷取外部緩存路徑

 * 2.如果擷取外部緩存路徑為空,擷取内部緩存路徑

 */

public static String getDiskCacheDir(Context context, String dirName) {

    String cachePath = null;

    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {//外部存儲挂載

        File externalCacheDir = context.getExternalCacheDir();

        if (externalCacheDir != null) {

            cachePath = externalCacheDir.getPath();

        }

    }

    if (cachePath == null) {

        File cacheDir = context.getCacheDir();

        if (cacheDir != null && cacheDir.exists()) {

            cachePath = cacheDir.getPath();

        }

    }

    return cachePath + File.separator + dirName;

}





/**

 * @param context

 * @param diskCachePath If null, use default appCacheDir+"/xBitmapCache"

 * BitmapGlobalConfig構造函數

 */

private BitmapGlobalConfig(Context context, String diskCachePath) {

    if (context == null) throw new IllegalArgumentException("context may not be null");

    this.mContext = context;

    this.diskCachePath = diskCachePath;

    initBitmapCache();

}
           

1.1.1 初始化磁盤與記憶體緩存initBitmapCache();

/**

 * 初始化記憶體緩存與磁盤緩存

 */

private void initBitmapCache() {

    new BitmapCacheManagementTask().execute(BitmapCacheManagementTask

            .MESSAGE_INIT_MEMORY_CACHE);

    new BitmapCacheManagementTask().execute(BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE);

}







    @Override

    protected Object[] doInBackground(Object... params) {

        if (params == null || params.length == 0) return params;

        BitmapCache cache = getBitmapCache();

        if (cache == null) return params;

        try {

            switch ((Integer) params[0]) {

                case MESSAGE_INIT_MEMORY_CACHE:

                    cache.initMemoryCache();//初始化記憶體緩存

                    break;

                case MESSAGE_INIT_DISK_CACHE:

                    cache.initDiskCache();//初始化磁盤緩存

                    break;

                case MESSAGE_FLUSH:

                    cache.flush();

                    break;

                case MESSAGE_CLOSE:

                    cache.clearMemoryCache();

                    cache.close();

                    break;

                case MESSAGE_CLEAR:

                    cache.clearCache();

                    break;

                case MESSAGE_CLEAR_MEMORY:

                    cache.clearMemoryCache();

                    break;

                case MESSAGE_CLEAR_DISK:

                    cache.clearDiskCache();

                    break;

                case MESSAGE_CLEAR_BY_KEY:

                    if (params.length != 2) return params;

                    cache.clearCache(String.valueOf(params[1]));

                    break;

                case MESSAGE_CLEAR_MEMORY_BY_KEY:

                    if (params.length != 2) return params;

                    cache.clearMemoryCache(String.valueOf(params[1]));

                    break;

                case MESSAGE_CLEAR_DISK_BY_KEY:

                    if (params.length != 2) return params;

                    cache.clearDiskCache(String.valueOf(params[1]));

                    break;

                default:

                    break;

            }

        } catch (Throwable e) {

            LogUtils.e(e.getMessage(), e);

        }

        return params;

    }
           

1.1.1.1 初始化記憶體緩存 initMemoryCache();

/**

 * Initialize the memory cache

 * 1.判斷是否打開記憶體緩存,未打開就傳回

 * 2.如果LruMemoryCache對象不為空就清空緩存

 * 3.LruMemoryCache對象為空就去建立對象覆寫方法傳回每個緩存單元的大小

 */

public void initMemoryCache() {

    if (!globalConfig.isMemoryCacheEnabled()) return;



    // Set up memory cache

    if (mMemoryCache != null) {

        try {

            clearMemoryCache();

        } catch (Throwable e) {

        }

    }

    mMemoryCache = new LruMemoryCache<MemoryCacheKey, Bitmap>(globalConfig.getMemoryCacheSize

            ()) {

        /**

         * Measure item size in bytes rather than units which is more practical

         * for a bitmap cache

         */

        @Override

        protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) {

            if (bitmap == null) return 0;

            return bitmap.getRowBytes() * bitmap.getHeight();

        }

    };

}
           

1.1.1.2 初始化磁盤緩存 initDiskCache() ;

/**

 * Initializes the disk cache.  Note that this includes disk access so this should not be

 * executed on the main/UI thread. By default an ImageCache does not initialize the disk

 * cache when it is created, instead you should call initDiskCache() to initialize it on a

 * background thread.

 * 初始化磁盤緩存

 * 1.擷取緩存路徑生成檔案目錄,确定可緩存磁盤占用空間大小

 * 2.根據條件open相應LruDiskCache對象

 * 3.設定檔案名設定器

 */

public void initDiskCache() {

    // Set up disk cache

    synchronized (mDiskCacheLock) {

        if (globalConfig.isDiskCacheEnabled() && (mDiskLruCache == null || mDiskLruCache

                .isClosed())) {

            File diskCacheDir = new File(globalConfig.getDiskCachePath());

            if (diskCacheDir.exists() || diskCacheDir.mkdirs()) {

                long availableSpace = OtherUtils.getAvailableSpace(diskCacheDir);//目錄總可用空間

                long diskCacheSize = globalConfig.getDiskCacheSize();//磁盤緩存大小

                diskCacheSize = availableSpace > diskCacheSize ? diskCacheSize : availableSpace;

                try {

                    //第一個參數指定的是資料的緩存位址,第二個參數指定目前應用程式的版本号,第三個參數指定同一個key可以對應多少個緩存檔案,基本都是傳1

                    // ,第四個參數指定最多可以緩存多少位元組的資料。

                    mDiskLruCache = LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);

                    mDiskLruCache.setFileNameGenerator(globalConfig.getFileNameGenerator());

                    LogUtils.d("create disk cache success");

                } catch (Throwable e) {

                    mDiskLruCache = null;

                    LogUtils.e("create disk cache error", e);

                }

            }

        }

    }

}





/**

 * OthreUtils.java

 * 傳回檔案對應目錄下可用空間

 * */

public static long getAvailableSpace(File dir) {

    try {

        final StatFs stats = new StatFs(dir.getPath());

        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();

    } catch (Throwable e) {

        LogUtils.e(e.getMessage(), e);

        return -1;

    }



}
           

1.1.1.2.1 建立LruDiskCache對象LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);

/**

 * Opens the cache in {@code directory}, creating a cache if none exists

 * there.

 *

 * @param directory  a writable directory

 * @param valueCount the number of values per cache entry. Must be positive.

 * @param maxSize    the maximum number of bytes this cache should use to store

 * @throws IOException if reading or writing the cache directory fails

 *                     <p/>

 *                     LruDiskCache.java

 *                     1.判斷傳入參數是否合理,不合理抛異常。

 *                     2.如果日志檔案存在,讀取日志内容生成緩存實體并設定,計算緩存檔案總大小。生成日志檔案輸入流

 *                     3.如果日志檔案不存在,建立檔案

 *                     4.傳回LruDiskCache對象

 */

public static LruDiskCache open(File directory, int appVersion, int valueCount, long maxSize)

        throws IOException {

    if (maxSize <= 0) {

        throw new IllegalArgumentException("maxSize <= 0");

    }

    if (valueCount <= 0) {

        throw new IllegalArgumentException("valueCount <= 0");

    }



    // If a bkp file exists, use it instead.

    File backupFile = new File(directory, JOURNAL_FILE_BACKUP);

    if (backupFile.exists()) {//日志備份檔案存在

        File journalFile = new File(directory, JOURNAL_FILE);

        // If journal file also exists just delete backup file.

        if (journalFile.exists()) {//源檔案也存在,删除備份

            backupFile.delete();

        } else {

            renameTo(backupFile, journalFile, false);

        }

    }



    // Prefer to pick up where we left off.

    LruDiskCache cache = new LruDiskCache(directory, appVersion, valueCount, maxSize);

    if (cache.journalFile.exists()) {//日志檔案存在

        try {

            cache.readJournal();//讀取日志

            cache.processJournal();//計算總大小

            cache.journalWriter = new BufferedWriter(new OutputStreamWriter(new

                    FileOutputStream(cache.journalFile, true), HTTP.US_ASCII));//寫檔案流

            return cache;

        } catch (Throwable journalIsCorrupt) {

            LogUtils.e("DiskLruCache " + directory + " is corrupt: " + journalIsCorrupt

                    .getMessage() + ", removing", journalIsCorrupt);

            cache.delete();

        }

    }



    // Create a new empty cache.

    //重建一個日志檔案。

    if (directory.exists() || directory.mkdirs()) {

        cache = new LruDiskCache(directory, appVersion, valueCount, maxSize);

        cache.rebuildJournal();

    }

    return cache;

}
           
1.1.1.2.1.1 讀取日志檔案cache.readJournal();
/**

 * 讀取日志檔案,設定緩存實體必要資訊。

 * 執行步驟:

 * 1.讀取日志頭部,判斷是否合理。

 * 2.循環讀取日志内容。

 */

private void readJournal() throws IOException {

    StrictLineReader reader = null;

    try {

        reader = new StrictLineReader(new FileInputStream(journalFile));

        String magic = reader.readLine();//第一行是個固定的字元串“libcore.io

        // .DiskLruCache”,标志着我們使用的是DiskLruCache技術。

        String version = reader.readLine();//第二行是DiskLruCache的版本号,這個值是恒為1的.

        String appVersionString = reader.readLine();//第三行是應用程式的版本号,我們在open()

        // 方法裡傳入的版本号是什麼這裡就會顯示什麼。

        String valueCountString = reader.readLine();//第四行是valueCount,這個值也是在open()

        // 方法中傳入的,通常情況下都為1。

        String blank = reader.readLine();//第五行是一個空行。

        if (!MAGIC.equals(magic) || !VERSION.equals(version) || !Integer.toString(appVersion)

                .equals(appVersionString) || !Integer.toString(valueCount).equals

                (valueCountString) || !"".equals(blank)) {

            throw new IOException("unexpected journal header: [" + magic + ", " +

                    "" + version + ", " + valueCountString + ", " + blank + "]");

        }



        int lineCount = 0;

        while (true) {//死循環讀取日志檔案

            try {

                readJournalLine(reader.readLine());

                lineCount++;

            } catch (EOFException endOfJournal) {

                break;

            }

        }

        redundantOpCount = lineCount - lruEntries.size();

    } finally {

        IOUtils.closeQuietly(reader);

    }

}





 /**

     * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},

     * this end of line marker is not included in the result.

     *

     * @return the next line from the input.

     * @throws IOException  for underlying {@code InputStream} errors.

     * @throws EOFException for the end of source stream.

     *                      StrictLineReader.java

     *                      讀取日志檔案

     *                      1.讀取檔案輸入到位元組數組,初始化位置變量

     *                      2.對位元組數組逐個周遊,遇到換行符構造字元串傳回并且更新位置變量

     *                      3.如果字元數組不存在換行符,建立ByteArrayOutputStream假設已讀出80位元組,覆寫toString

     *                      4.死循環内循環判斷遇到換行符ByteArrayOutputStream寫出到位元組緩存,更新位置變量,傳回toString

     */

    public String readLine() throws IOException {

        synchronized (in) {

            if (buf == null) {

                throw new IOException("LineReader is closed");

            }



            // Read more data if we are at the end of the buffered data.

            // Though it's an error to read after an exception, we will let {@code fillBuf()}

            // throw again if that happens; thus we need to handle end == -1 as well as end

            // == pos.

            //初始化pos與end變量

            if (pos >= end) {

                fillBuf();

            }

            // Try to find LF in the buffered data and return the line if successful.

            for (int i = pos; i != end; ++i) {

                if (buf[i] == LF) {//新行

                    int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;//lineEnd之後為換行

                    String res = new String(buf, pos, lineEnd - pos, charset.name());

                    pos = i + 1;//重置起始位

                    return res;

                }

            }



            // Let's anticipate up to 80 characters on top of those already read.

            //假定80個字元已經讀出

            ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {

                @Override

                public String toString() {

                    int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;

                    try {

                        return new String(buf, 0, length, charset.name());

                    } catch (UnsupportedEncodingException e) {

                        throw new AssertionError(e); // Since we control the charset this

                        // will never happen.

                    }

                }

            };



            while (true) {

                out.write(buf, pos, end - pos);//将資料寫入位元組數組緩存

                // Mark unterminated line in case fillBuf throws EOFException or IOException.

                end = -1;

                fillBuf();

                // Try to find LF in the buffered data and return the line if successful.

                for (int i = pos; i != end; ++i) {

                    if (buf[i] == LF) {//新行

                        if (i != pos) {

                            out.write(buf, pos, i - pos);//寫出這一行

                        }

                        out.flush();

                        pos = i + 1;

                        return out.toString();

                    }

                }

            }

        }

    }



  /**

     * Reads new input data into the buffer. Call only with pos == end or end == -1,

     * depending on the desired outcome if the function throws.

     */

    private void fillBuf() throws IOException {

        int result = in.read(buf, 0, buf.length);

        if (result == -1) {

            throw new EOFException();

        }

        pos = 0;

        end = result;

    }
           
1.1.1.2.1.1.1 readJournalLine(reader.readLine());
/**

 * 讀取日志檔案内容。

 * 注意日志格式例如:D xxxxxxxx

 * 第六行是以一個DIRTY字首開始的,後面緊跟着緩存圖檔的key。

 * 通常我們看到DIRTY這個字樣都不代表着什麼好事情,意味着這是一條髒資料。

 * 沒錯,每當我們調用一次DiskLruCache的edit()方法時,都會向journal檔案中寫入一條DIRTY記錄,表示我們正準備寫入一條緩存資料,但不知結果如何。

 * 然後調用commit()方法表示寫入緩存成功,這時會向journal中寫入一條CLEAN記錄,意味着這條“髒”資料被“洗幹淨了”,調用abort()

 * 方法表示寫入緩存失敗,這時會向journal中寫入一條REMOVE記錄。

 * 也就是說,每一行DIRTY的key,後面都應該有一行對應的CLEAN或者REMOVE的記錄,否則這條資料就是“髒”的,會被自動删除掉。

 * 除了CLEAN字首和key之外,後面還有一個152313,這是什麼意思呢?

 * 其實,DiskLruCache會在每一行CLEAN記錄的最後加上該條緩存資料的大小,以位元組為機關。

 * <p/>

 * 執行步驟:

 * 1.根據第一個空格解析字元串擷取指令辨別:CRUD

 * 2.擷取第二個空格索引,如果不存在第二個空格擷取緩存的key,并且指令辨別為D,就删除緩存實體函數傳回。

 * 如果存在第二個空格,得到緩存檔案的key

 * 3.根據key擷取緩存的實體,如果不存在就建立。

 * 4.根據指令辨別符執行操作。

 * 對于CLEAN格式:C XXXXXX txxxx 123000,首先判斷是否有過期字首,如果存在儲存過期時間不存在設定為最大值。接着儲存緩存檔案大小。

 * 對于UDPATE:擷取Editor對象。

 */

private void readJournalLine(String line) throws IOException {

    int firstSpace = line.indexOf(' ');

    char lineTag = 0;

    if (firstSpace == 1) {//指令辨別

        lineTag = line.charAt(0);

    } else {

        throw new IOException("unexpected journal line: " + line);

    }



    int keyBegin = firstSpace + 1;

    int secondSpace = line.indexOf(' ', keyBegin);//第二個空格索引

    final String diskKey;

    if (secondSpace == -1) {//不存在第二個空格

        diskKey = line.substring(keyBegin);//擷取緩存檔案的key

        if (lineTag == DELETE) {//删除指令

            lruEntries.remove(diskKey);//移除這個key

            return;

        }

    } else {//存在第二個空格

        diskKey = line.substring(keyBegin, secondSpace);//擷取緩存檔案的key

    }



    Entry entry = lruEntries.get(diskKey);//緩存實體

    if (entry == null) {

        entry = new Entry(diskKey);

        lruEntries.put(diskKey, entry);

    }



    switch (lineTag) {

        case CLEAN: {

            entry.readable = true;

            entry.currentEditor = null;

            String[] parts = line.substring(secondSpace + 1).split(" ");

            if (parts.length > 0) {

                try {

                    if (parts[0].charAt(0) == EXPIRY_PREFIX) {//過期字首

                        entry.expiryTimestamp = Long.valueOf(parts[0].substring(1));

                        entry.setLengths(parts, 1);//設定緩存檔案的大小

                    } else {//不存在過期字首

                        entry.expiryTimestamp = Long.MAX_VALUE;

                        entry.setLengths(parts, 0);//設定緩存檔案的大小

                    }

                } catch (Throwable e) {

                    throw new IOException("unexpected journal line: " + line);

                }

            }

            break;

        }

        case UPDATE: {

            entry.currentEditor = new Editor(entry);

            break;

        }

        case READ: {

            // This work was already done by calling lruEntries.get().

            break;

        }

        default: {

            throw new IOException("unexpected journal line: " + line);

        }

    }

}
           
1.1.1.2.1.2 計算總大小 cache.processJournal();
/**

 * Computes the initial size and collects garbage as a part of opening the

 * cache. Dirty entries are assumed to be inconsistent and will be deleted.

 * 執行步驟:

 * 1.删除日志臨時檔案

 * 2.疊代實體,如果不是update直接累加計算緩存檔案總大小,是update删除實體對應的dirty及clean檔案。

 */

private void processJournal() throws IOException {

    deleteIfExists(journalFileTmp);//删除日志臨時檔案

    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {

        Entry entry = i.next();

        if (entry.currentEditor == null) {

            for (int t = 0; t < valueCount; t++) {

                size += entry.lengths[t];//累計計算緩存檔案總大小

            }

        } else {//update狀态删除實體的Dirty和Clean檔案    ?

            entry.currentEditor = null;

            for (int t = 0; t < valueCount; t++) {

                deleteIfExists(entry.getCleanFile(t));

                deleteIfExists(entry.getDirtyFile(t));

            }

            i.remove();

        }

    }

}
           

2.顯示圖檔, mBitmapUtils.display(mRecyclerViewHolder.mArticleImage, article.getPhotoURL());

public <T extends View> void display(T container, String uri) {

    display(container, uri, null, null);

}



public <T extends View> void display(T container, String uri, BitmapDisplayConfig

        displayConfig) {

    display(container, uri, displayConfig, null);

}



public <T extends View> void display(T container, String uri, BitmapLoadCallBack<T> callBack) {

    display(container, uri, null, callBack);

}



 /**

 * 1.設定加載回調,設定展示配置BitmapDisplayConfig,設定Bitmap大小參數

 * 2.先從記憶體中擷取Bitmap,如果存在設定回調狀态

 * 3.記憶體中不存在,檢查如果加載Bitmap任務不存在,建立BitmapLoadTask對象獲得PriorityExecutor線程池加載器

 * 4.從磁盤擷取緩存檔案,緩存存在并且線程池加載器正在忙,切換磁盤緩存加載器

 * 5.獲得配置正在加載Drawable對象封裝添加對應Task的AsyncDrawable對象

 * 6.回調接口設定該AsyncDrawable對象

 * 7.傳入線程池加載器執行BitmapLoadTask對象

 */

public <T extends View> void display(T container, String uri, BitmapDisplayConfig

        displayConfig, BitmapLoadCallBack<T> callBack) {

    if (container == null) {

        return;

    }



    if (callBack == null) {

        callBack = new DefaultBitmapLoadCallBack<T>();//預設圖檔加載回調

    }



    if (displayConfig == null || displayConfig == defaultDisplayConfig) {

        displayConfig = defaultDisplayConfig.cloneNew();

    }



    // Optimize Max Size

    BitmapSize size = displayConfig.getBitmapMaxSize();

    displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size

            .getWidth(), size.getHeight()));//設定Bitmap顯示的最大值



    container.clearAnimation();//清除動畫



    if (TextUtils.isEmpty(uri)) {//下載下傳位址為空,回調失敗傳回

        callBack.onLoadFailed(container, uri, displayConfig.getLoadFailedDrawable());

        return;

    }



    // start loading

    callBack.onPreLoad(container, uri, displayConfig);



    // find bitmap from mem cache.先從記憶體中擷取Bitmap

    Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);



    if (bitmap != null) {//記憶體中存在Bitmap,設定回調狀态結果

        callBack.onLoadStarted(container, uri, displayConfig);

        callBack.onLoadCompleted(container, uri, bitmap, displayConfig, BitmapLoadFrom

                .MEMORY_CACHE);

    } else if (!bitmapLoadTaskExist(container, uri, callBack)) {//bitmap加載任務不存在



        final BitmapLoadTask<T> loadTask = new BitmapLoadTask<T>(container, uri,

                displayConfig, callBack);//建立下載下傳任務



        // get executor  獲得加載執行器

        PriorityExecutor executor = globalConfig.getBitmapLoadExecutor();

        File diskCacheFile = this.getBitmapFileFromDiskCache(uri);//從磁盤中擷取緩存

        boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists();

        if (diskCacheExist && executor.isBusy()) {//檔案存在,并且Bitmap加載線程池忙

            executor = globalConfig.getDiskCacheExecutor();//擷取磁盤緩存處理線程池執行器

        }

        // set loading image

        Drawable loadingDrawable = displayConfig.getLoadingDrawable();//設定正在加載顯示的圖檔

        callBack.setDrawable(container, new AsyncDrawable<T>(loadingDrawable, loadTask));

        loadTask.setPriority(displayConfig.getPriority());

        loadTask.executeOnExecutor(executor);

    }

}
           

2.1 設定Bitmap顯示的最大值displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size.getWidth(), size.getHeight()));

/**

 * BitmapCommonUtils.java

 * 設定Bitmap大小傳回BitmapSize。

 * 1.如果使用者設定了最大最小值,直接構造傳回BitmapSize對象。

 * 2.擷取View的Layout參數設定大小

 * 3.如果上述得到值小于0,反射擷取ImageView的“mMaxWidth”“mMaxHeight”值

 * 4.如果上述得到值小于0,擷取視窗大小,并進行設定。

 * */

public static BitmapSize optimizeMaxSizeByView(View view, int maxImageWidth,

                                               int maxImageHeight) {

    int width = maxImageWidth;

    int height = maxImageHeight;



    if (width > 0 && height > 0) {

        return new BitmapSize(width, height);

    }



    final ViewGroup.LayoutParams params = view.getLayoutParams();

    if (params != null) {//根據父容器參數設定View顯示大小

        if (params.width > 0) {

            width = params.width;

        } else if (params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {

            width = view.getWidth();

        }



        if (params.height > 0) {

            height = params.height;

        } else if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {

            height = view.getHeight();

        }

    }



    if (width <= 0) width = getImageViewFieldValue(view, "mMaxWidth");//根據ImageView聲明字段擷取大小

    if (height <= 0) height = getImageViewFieldValue(view, "mMaxHeight");



    BitmapSize screenSize = getScreenSize(view.getContext());

    if (width <= 0) width = screenSize.getWidth();//根據視窗大小設定大小

    if (height <= 0) height = screenSize.getHeight();



    return new BitmapSize(width, height);

}



  /**

 * 獲得ImageView聲明的mMaxWidth,mMaxHeight字段數值

 */

private static int getImageViewFieldValue(Object object, String fieldName) {

    int value = 0;

    if (object instanceof ImageView) {

        try {

            Field field = ImageView.class.getDeclaredField(fieldName);

            field.setAccessible(true);

            int fieldValue = (Integer) field.get(object);

            if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {//不是預設值

                value = fieldValue;

            }

        } catch (Throwable e) {

        }

    }

    return value;

}
           

2.2 從記憶體緩存中擷取Bitmap,globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);

public BitmapCache getBitmapCache() {

    if (bitmapCache == null) {

        bitmapCache = new BitmapCache(this);

    }

    return bitmapCache;

}



 /**

 * Get the bitmap from memory cache.

 *

 * @param uri    Unique identifier for which item to get

 * @param config

 * @return The bitmap if found in cache, null otherwise

 * 從記憶體緩存中查找是否存在Bitmap

 */

public Bitmap getBitmapFromMemCache(String uri, BitmapDisplayConfig config) {

    if (mMemoryCache != null && globalConfig.isMemoryCacheEnabled()) {

        MemoryCacheKey key = new MemoryCacheKey(uri, config);

        return mMemoryCache.get(key);

    }

    return null;

}
           

2.3 判斷Bitmap加載任務是否存在,bitmapLoadTaskExist(container, uri, callBack)

/**

 * 判斷目前bitmap加載任務是否已經存在

 * 1.傳入container,獲得Drawable對象強制轉換判斷時候存在下載下傳任務

 * 2.如果存在下載下傳任務,在判斷該任務與最新任務差異,存在就取消已存在任務

 * 3.不存在就傳回false

 */

private static <T extends View> boolean bitmapLoadTaskExist(T container, String uri,

                                                            BitmapLoadCallBack<T> callBack) {

    final BitmapLoadTask<T> oldLoadTask = getBitmapTaskFromContainer(container, callBack);



    if (oldLoadTask != null) {

        final String oldUrl = oldLoadTask.uri;

        if (TextUtils.isEmpty(oldUrl) || !oldUrl.equals(uri)) {

            oldLoadTask.cancel(true);//取消舊的下載下傳任務

        } else {

            return true;

        }

    }

    return false;

}



 private static <T extends View> BitmapLoadTask<T> getBitmapTaskFromContainer(T container,

                                                                             BitmapLoadCallBack<T> callBack) {

    if (container != null) {

        final Drawable drawable = callBack.getDrawable(container);

        if (drawable instanceof AsyncDrawable) {//擷取BitmapTask對象

            final AsyncDrawable<T> asyncDrawable = (AsyncDrawable<T>) drawable;

            return asyncDrawable.getBitmapWorkerTask();

        }

    }

    return null;

}
           

2.4 bitmap加載任務不存在,從磁盤中擷取緩存 this.getBitmapFileFromDiskCache(uri);

public File getBitmapFileFromDiskCache(String uri) {

    return globalConfig.getBitmapCache().getBitmapFileFromDiskCache(uri);

}



/**

 * Get the bitmap file from disk cache.

 *

 * @param uri Unique identifier for which item to get

 * @return The file if found in cache.

 */

public File getBitmapFileFromDiskCache(String uri) {

    synchronized (mDiskCacheLock) {

        if (mDiskLruCache != null) {

            return mDiskLruCache.getCacheFile(uri, DISK_CACHE_INDEX);

        } else {

            return null;

        }

    }

}



/**LruDiskCache.java

 * 擷取緩存檔案

 */

public File getCacheFile(String key, int index) {

    String diskKey = fileNameGenerator.generate(key);//生成key

    File result = new File(this.directory, diskKey + "." + index);

    if (result.exists()) {

        return result;

    } else {

        try {

            this.remove(key);

        } catch (IOException ignore) {

        }

        return null;

    }

}
           

2.5執行Btimap加載人任務loadTask.executeOnExecutor(executor);

/**

     * BitmapLoadTask.java

     * 背景線程執行

     * 1.死循環判斷暫定狀态,并阻塞線程

     * 2.釋出狀态,從磁盤緩存中擷取Birmap

     * 3.磁盤緩存中不存在去下載下傳

     */

    @Override

    protected Bitmap doInBackground(Object... params) {

        synchronized (pauseTaskLock) {

            while (pauseTask && !this.isCancelled()) {

                try {

                    pauseTaskLock.wait();//線程阻塞

                    if (cancelAllTask) {

                        return null;

                    }

                } catch (Throwable e) {

                }

            }

        }

        Bitmap bitmap = null;

        // get cache from disk cache

        if (!this.isCancelled() && this.getTargetContainer() != null) {

            this.publishProgress(PROGRESS_LOAD_STARTED);

            bitmap = globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);

        }

        // download image

        if (bitmap == null && !this.isCancelled() && this.getTargetContainer() != null)

        {//磁盤緩存不存在,去下載下傳

            bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);

            from = BitmapLoadFrom.URI;

        }

        return bitmap;

    }
           

2.5.1 擷取磁盤緩存的Bitmap,globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);

/**

 * Get the bitmap from disk cache.

 * @param uri

 * @param config

 * @return

 * BitmapCache.java

 * 從磁盤緩存中擷取Bitmap

 * 1.檢查LruDiskCache是否存在,初始化LruDiskCache對象

 * 2.根據傳入url擷取磁盤緩存處理Snapshot對象,如果為null即不存在緩存,直接傳回null

 * 3.判斷配置是否為空或者顯示原圖,如果是,直接根據輸入流構造Bitmap。否則根據傳入配置壓縮Bitmap

 * 4.根據需要處理Bitmap旋轉

 * 5.将Bitmap添加到記憶體緩存中,傳回Bitmap

 */

public Bitmap getBitmapFromDiskCache(String uri, BitmapDisplayConfig config) {

    if (uri == null || !globalConfig.isDiskCacheEnabled()) return null;

    if (mDiskLruCache == null) {

        initDiskCache();

    }

    if (mDiskLruCache != null) {

        LruDiskCache.Snapshot snapshot = null;

        try {

            snapshot = mDiskLruCache.get(uri);

            if (snapshot != null) {

                Bitmap bitmap = null;

                if (config == null || config.isShowOriginal()) {//顯示原圖

                    bitmap = BitmapDecoder.decodeFileDescriptor(snapshot.getInputStream

                            (DISK_CACHE_INDEX).getFD());

                } else {//圖檔壓縮

                    bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(snapshot

                            .getInputStream(DISK_CACHE_INDEX).getFD(), config

                            .getBitmapMaxSize(), config.getBitmapConfig());

                }

                bitmap = rotateBitmapIfNeeded(uri, config, bitmap);//圖檔旋轉

                bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache

                        .getExpiryTimestamp(uri));

                return bitmap;

            }

        } catch (Throwable e) {

            LogUtils.e(e.getMessage(), e);

        } finally {

            IOUtils.closeQuietly(snapshot);

        }

    }

    return null;

}
           
2.5.1.1 擷取snapshot = mDiskLruCache.get(uri);
public Snapshot get(String key) throws IOException {

    String diskKey = fileNameGenerator.generate(key);

    return getByDiskKey(diskKey);

}



/**

 * Returns a snapshot of the entry named {@code diskKey}, or null if it doesn't

 * exist is not currently readable. If a value is returned, it is moved to

 * the head of the LRU queue.

 * 1.根據key擷取對應Entry對象,檢查是否時間過期,如果過期就删除該Entry對象内的所有緩存檔案,删除過期對象檢查是否需要重構日志檔案,最後傳回null

 * 2.列舉該Entry對象對應所有緩存檔案的輸入流,如果遇到不存在的檔案異常關閉輸入流傳回null

 * 3.再次檢查是否重構日志,同時建立Snapshot對象傳回

 */

private synchronized Snapshot getByDiskKey(String diskKey) throws IOException {

    checkNotClosed();

    Entry entry = lruEntries.get(diskKey);

    if (entry == null) {

        return null;

    }

    if (!entry.readable) {

        return null;

    }



    // If expired, delete the entry.

    if (entry.expiryTimestamp < System.currentTimeMillis()) {//删除過期

        for (int i = 0; i < valueCount; i++) {

            File file = entry.getCleanFile(i);

            if (file.exists() && !file.delete()) {

                throw new IOException("failed to delete " + file);

            }

            size -= entry.lengths[i];

            entry.lengths[i] = 0;

        }

        redundantOpCount++;

        journalWriter.append(DELETE + " " + diskKey + '\n');//添加删除記錄

        lruEntries.remove(diskKey);

        if (journalRebuildRequired()) {//重構日志檔案

            executorService.submit(cleanupCallable);

        }

        return null;

    }

    // Open all streams eagerly to guarantee that we see a single published

    // snapshot. If we opened streams lazily then the streams could come

    // from different edits.

    //列舉該key對應的所有輸入流,對不存在

    FileInputStream[] ins = new FileInputStream[valueCount];

    try {

        for (int i = 0; i < valueCount; i++) {

            ins[i] = new FileInputStream(entry.getCleanFile(i));

        }

    } catch (FileNotFoundException e) {

        // A file must have been deleted manually!

        for (int i = 0; i < valueCount; i++) {

            if (ins[i] != null) {

                IOUtils.closeQuietly(ins[i]);

            } else {

                break;

            }

        }

        return null;

    }

    journalWriter.append(READ + " " + diskKey + '\n');//添加讀取記錄

    if (journalRebuildRequired()) {

        executorService.submit(cleanupCallable);

    }
           
2.5.1.2 構造原圖bitmap = BitmapDecoder.decodeFileDescriptor(snapshot.getInputStream(DISK_CACHE_INDEX).getFD());
/***

 * BitmapFactory.Options.inPurgeable;

 * BitmapDecoder.java

 * <p/>

 * 如果 inPurgeable 設為True的話表示使用BitmapFactory建立的Bitmap

 * 用于存儲Pixel的記憶體空間在系統記憶體不足時可以被回收,

 * 在應用需要再次通路Bitmap的Pixel時(如繪制Bitmap或是調用getPixel),

 * 系統會再次調用BitmapFactory decoder重新生成Bitmap的Pixel數組。

 * 為了能夠重新解碼圖像,bitmap要能夠通路存儲Bitmap的原始資料。

 * <p/>

 * 在inPurgeable為false時表示建立的Bitmap的Pixel記憶體空間不能被回收,

 * 這樣BitmapFactory在不停decodeByteArray建立新的Bitmap對象,

 * 不同裝置的記憶體不同,是以能夠同時建立的Bitmap個數可能有所不同,

 * 200個bitmap足以使大部分的裝置重新OutOfMemory錯誤。

 * 當isPurgable設為true時,系統中記憶體不足時,

 * 可以回收部分Bitmap占據的記憶體空間,這時一般不會出現OutOfMemory 錯誤。

 */

public static Bitmap decodeFileDescriptor(FileDescriptor fileDescriptor) {

    synchronized (lock) {

        final BitmapFactory.Options options = new BitmapFactory.Options();

        options.inPurgeable = true;

        options.inInputShareable = true; // 與inPurgeable 一起使用

        try {

            return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        } catch (Throwable e) {

            LogUtils.e(e.getMessage(), e);

            return null;

        }

    }

}
           
2.5.1.3 構造縮放bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(snapshot.getInputStream(DISK_CACHE_INDEX).getFD(), config.getBitmapMaxSize(), config.getBitmapConfig());
public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor,

                                                       BitmapSize maxSize, Bitmap.Config

                                                               config) {

    synchronized (lock) {

        final BitmapFactory.Options options = new BitmapFactory.Options();

        options.inJustDecodeBounds = true;//隻是擷取Bitmap參數

        options.inPurgeable = true;

        options.inInputShareable = true;

        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        options.inSampleSize = calculateInSampleSize(options, maxSize.getWidth(), maxSize

                .getHeight());

        options.inJustDecodeBounds = false;

        if (config != null) {

            options.inPreferredConfig = config;

        }

        try {

            return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        } catch (Throwable e) {

            LogUtils.e(e.getMessage(), e);

            return null;

        }

    }

}



 /**

 * BitmapDecoder.java

 * 計算實際Bitmap尺寸

 */

public static int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int

        maxHeight) {

    final int height = options.outHeight;//測量獲得Bitmap尺寸

    final int width = options.outWidth;

    int inSampleSize = 1;

    if (width > maxWidth || height > maxHeight) {

        if (width > height) {

            inSampleSize = Math.round((float) height / (float) maxHeight);//四舍五入取整

        } else {

            inSampleSize = Math.round((float) width / (float) maxWidth);

        }



        final float totalPixels = width * height;



        final float maxTotalPixels = maxWidth * maxHeight * 2;



        while (totalPixels / (inSampleSize * inSampleSize) > maxTotalPixels) {//?

            inSampleSize++;

        }

    }

    return inSampleSize;

}
           
2.5.1.4 處理圖檔旋轉,bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
/**

 * 1.擷取配置判斷是否自動旋轉

 * 2.如果是,擷取Bitmap檔案構造ExifInterface對象,擷取圖檔方向參數

 * 3.如果方向不是0,矯正Bitmap方法傳回

 */

private synchronized Bitmap rotateBitmapIfNeeded(String uri, BitmapDisplayConfig config,

                                                 Bitmap bitmap) {

    Bitmap result = bitmap;

    if (config != null && config.isAutoRotation()) {

        File bitmapFile = this.getBitmapFileFromDiskCache(uri);

        if (bitmapFile != null && bitmapFile.exists()) {//擷取緩存Bitmap檔案對象

            ExifInterface exif = null;//這個接口提供了圖檔檔案的旋轉,gps,時間等資訊。

            try {

                exif = new ExifInterface(bitmapFile.getPath());

            } catch (Throwable e) {

                return result;

            }

            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,

                    ExifInterface.ORIENTATION_UNDEFINED);//擷取圖檔方向參數

            int angle = 0;

            switch (orientation) {

                case ExifInterface.ORIENTATION_ROTATE_90:

                    angle = 90;

                    break;

                case ExifInterface.ORIENTATION_ROTATE_180:

                    angle = 180;

                    break;

                case ExifInterface.ORIENTATION_ROTATE_270:

                    angle = 270;

                    break;

                default:

                    angle = 0;

                    break;

            }

            if (angle != 0) {

                Matrix m = new Matrix();

                m.postRotate(angle);

                result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap

                        .getHeight(), m, true);//重新構造Bitmap

                bitmap.recycle();

                bitmap = null;

            }

        }

    }

    return result;

}
           
2.5.1.5 将Bitmap添加到記憶體緩存中 bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache.getExpiryTimestamp(uri));
/**

 * 将Bitmap添加到記憶體中

 */

private Bitmap addBitmapToMemoryCache(String uri, BitmapDisplayConfig config, Bitmap bitmap,

                                      long expiryTimestamp) throws IOException {

    if (config != null) {

        BitmapFactory bitmapFactory = config.getBitmapFactory();

        if (bitmapFactory != null) {//?

            bitmap = bitmapFactory.cloneNew().createBitmap(bitmap);

        }

    }

    if (uri != null && bitmap != null && globalConfig.isMemoryCacheEnabled() && mMemoryCache

            != null) {

        MemoryCacheKey key = new MemoryCacheKey(uri, config);

        mMemoryCache.put(key, bitmap, expiryTimestamp);//添加到記憶體緩存

    }

    return bitmap;

}
           

2.5.2 磁盤緩存Bitmap不存在,去下載下傳bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);

/**

 * BitmapCache.java

 * 下載下傳Bitmap

 * 1.如果配置開啟磁盤緩存,下載下傳Bitmap到dirtyFile,根據結果clean或者delete,構造Bitmap

 * 2.如果上述下載下傳Bitmap為null,将Bitmap下載下傳到記憶體輸出流中,構造Bitmap

 * 3.處理Bitmap旋轉以及添加到記憶體緩存中

 */

public Bitmap downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils

        .BitmapLoadTask<?> task) {



    BitmapMeta bitmapMeta = new BitmapMeta();



    OutputStream outputStream = null;

    LruDiskCache.Snapshot snapshot = null;



    try {

        Bitmap bitmap = null;



        // try download to disk,下載下傳到磁盤緩存

        if (globalConfig.isDiskCacheEnabled()) {

            if (mDiskLruCache == null) {

                initDiskCache();

            }



            if (mDiskLruCache != null) {

                try {

                    snapshot = mDiskLruCache.get(uri);

                    if (snapshot == null) {//緩存不存在

                        LruDiskCache.Editor editor = mDiskLruCache.edit(uri);

                        if (editor != null) {

                            outputStream = editor.newOutputStream(DISK_CACHE_INDEX);//dirtyFile

                            bitmapMeta.expiryTimestamp = globalConfig.getDownloader()

                                    .downloadToStream(uri, outputStream, task);

                            //下載下傳Bitmap存儲到dirtyFile

                            if (bitmapMeta.expiryTimestamp < 0) {//下載下傳出錯

                                editor.abort();

                                return null;

                            } else {//下載下傳成功

                                editor.setEntryExpiryTimestamp(bitmapMeta.expiryTimestamp);

                                editor.commit();

                            }

                            snapshot = mDiskLruCache.get(uri);

                        }

                    }

                    if (snapshot != null) {

                        bitmapMeta.inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);

                        bitmap = decodeBitmapMeta(bitmapMeta, config);//構造Bitmap

                        if (bitmap == null) {//擷取的Bitmap為null,删除對應clean檔案

                            bitmapMeta.inputStream = null;

                            mDiskLruCache.remove(uri);

                        }

                    }

                } catch (Throwable e) {

                    LogUtils.e(e.getMessage(), e);

                }

            }

        }



        // try download to memory stream,

        if (bitmap == null) {

            outputStream = new ByteArrayOutputStream();

            bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri,

                    outputStream, task);

            if (bitmapMeta.expiryTimestamp < 0) {//下載下傳失敗了

                return null;

            } else {//成功,去生成Bitmap

                bitmapMeta.data = ((ByteArrayOutputStream) outputStream).toByteArray();

                bitmap = decodeBitmapMeta(bitmapMeta, config);

            }

        }



        if (bitmap != null) {//處理旋轉,添加到記憶體緩存

            bitmap = rotateBitmapIfNeeded(uri, config, bitmap);

            bitmap = addBitmapToMemoryCache(uri, config, bitmap, bitmapMeta.expiryTimestamp);

        }

        return bitmap;

    } catch (Throwable e) {

        LogUtils.e(e.getMessage(), e);

    } finally {

        IOUtils.closeQuietly(outputStream);

        IOUtils.closeQuietly(snapshot);

    }



    return null;

}
           
2.5.2.1 擷取Editor對象,mDiskLruCache.edit(uri);
/**

 * Returns an editor for the entry named {@code Key}, or null if another

 * edit is in progress.

 */

public Editor edit(String key) throws IOException {

    String diskKey = fileNameGenerator.generate(key);

    return editByDiskKey(diskKey, ANY_SEQUENCE_NUMBER);

}



/**

 * 擷取Editor對象

 * 1.檢查是否存在Entry對象,不存在建立添加到緩存

 * 2.建立Editor對象,添加到Entry對象添加日志。

 */

private synchronized Editor editByDiskKey(String diskKey, long expectedSequenceNumber) throws

        IOException {

    checkNotClosed();

    Entry entry = lruEntries.get(diskKey);

    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry

            .sequenceNumber != expectedSequenceNumber)) {

        return null; // Snapshot is stale.?

    }

    if (entry == null) {//不存在緩存記錄

        entry = new Entry(diskKey);

        lruEntries.put(diskKey, entry);

    } else if (entry.currentEditor != null) {

        return null; // Another edit is in progress.

    }



    Editor editor = new Editor(entry);//建立

    entry.currentEditor = editor;



    // Flush the journal before creating files to prevent file leaks.

    journalWriter.write(UPDATE + " " + diskKey + '\n');

    journalWriter.flush();

    return editor;

}
           
2.5.2.2 擷取dirtyFile輸出流outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
/**

     * Returns a new unbuffered output stream to write the value at

     * {@code index}. If the underlying output stream encounters errors

     * when writing to the filesystem, this edit will be aborted when

     * {@link #commit} is called. The returned output stream does not throw

     * IOExceptions.

     * LruDiskCache.java

     * 1.判斷Entry對象目前的Editor應用是否與此相同,不同異常

     * 2.取出Entry對象對應的dirtyFile,建立輸出流并傳回

     */

    public OutputStream newOutputStream(int index) throws IOException {

        synchronized (LruDiskCache.this) {

            if (entry.currentEditor != this) {

                throw new IllegalStateException();

            }

            if (!entry.readable) {

                written[index] = true;

            }

            File dirtyFile = entry.getDirtyFile(index);

            FileOutputStream outputStream;

            try {

                outputStream = new FileOutputStream(dirtyFile);

            } catch (FileNotFoundException e) {

                // Attempt to recreate the cache directory.

                directory.mkdirs();

                try {

                    outputStream = new FileOutputStream(dirtyFile);

                } catch (FileNotFoundException e2) {

                    // We are unable to recover. Silently eat the writes.

                    return NULL_OUTPUT_STREAM;

                }

            }

            return new FaultHidingOutputStream(outputStream);

        }

    }
           
2.5.2.3 去下載下傳BitmapbitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);
/**

 * Download bitmap to outputStream by uri.

 *

 * @param uri          file path, assets path(assets/xxx) or http url.

 * @param outputStream

 * @param task

 * @return The expiry time stamp or -1 if failed to download.

 * 下載下傳檔案到dirtyFile

 * 1.url是檔案路徑,擷取響應輸入流和檔案長度,構造過期時間戳

 * 2.url是assets路徑擷取響應輸入流和檔案長度,構造過期時間戳為最大值

 * 3.url是網址,擷取URLConnection,擷取輸入流,構造時間戳

 * 4.将擷取的輸入流讀入資源,寫出到提供的輸出流

 * 5.傳回過期時間戳

 */

@Override

public long downloadToStream(String uri, OutputStream outputStream, final BitmapUtils

        .BitmapLoadTask<?> task) {



    if (task == null || task.isCancelled() || task.getTargetContainer() == null) return -1;



    URLConnection urlConnection = null;

    BufferedInputStream bis = null;



    OtherUtils.trustAllHttpsURLConnection();



    long result = -1;

    long fileLen = 0;

    long currCount = 0;

    try {

        if (uri.startsWith("/")) {//url是一個檔案路徑

            FileInputStream fileInputStream = new FileInputStream(uri);

            fileLen = fileInputStream.available();//該方法傳回可估算從這個輸入流中可無阻塞讀取剩餘的位元組數。

            bis = new BufferedInputStream(fileInputStream);

            result = System.currentTimeMillis() + this.getDefaultExpiry();

        } else if (uri.startsWith("assets/")) {//assets檔案夾中的資源

            InputStream inputStream = this.getContext().getAssets().open(uri.substring(7, uri

                    .length()));



            fileLen = inputStream.available();

            bis = new BufferedInputStream(inputStream);

            result = Long.MAX_VALUE;

        } else {//設定一個資源網址

            final URL url = new URL(uri);

            urlConnection = url.openConnection();

            urlConnection.setConnectTimeout(this.getDefaultConnectTimeout());

            urlConnection.setReadTimeout(this.getDefaultReadTimeout());

            bis = new BufferedInputStream(urlConnection.getInputStream());

            result = urlConnection.getExpiration();//響應過期時間戳

            result = result < System.currentTimeMillis() ? System.currentTimeMillis() + this

                    .getDefaultExpiry() : result;

            fileLen = urlConnection.getContentLength();

        }



        if (task.isCancelled() || task.getTargetContainer() == null) return -1;



        byte[] buffer = new byte[4096];

        int len = 0;

        BufferedOutputStream out = new BufferedOutputStream(outputStream);

        while ((len = bis.read(buffer)) != -1) {

            out.write(buffer, 0, len);//向提供的輸出流寫出,這個輸出流一般是dirtyFile

            currCount += len;

            if (task.isCancelled() || task.getTargetContainer() == null) return -1;

            task.updateProgress(fileLen, currCount);

        }

        out.flush();

    } catch (Throwable e) {

        result = -1;

        LogUtils.e(e.getMessage(), e);

    } finally {

        IOUtils.closeQuietly(bis);

    }

    return result;

}
           
2.5.2.4 下載下傳檔案出錯editor.abort();
/**

 * Aborts this edit. This releases the edit lock so another edit may be

 * started on the same key.

 */

public void abort() throws IOException {

    completeEdit(this, false);

}



/**

 * 1.檢查初次生成entry的index時候有值

 * 2.周遊所有緩存檔案,如果狀态為success,将存在的dirty檔案清洗,緩存大小資訊

 * 3.如果狀态為失敗,删除所有dirty檔案

 * 4.更新日志檔案

 */

private synchronized void completeEdit(Editor editor, boolean success) throws IOException {

    Entry entry = editor.entry;

    if (entry.currentEditor != editor) {

        throw new IllegalStateException();

    }



    // If this edit is creating the entry for the first time, every index must have a value.

    if (success && !entry.readable) {

        for (int i = 0; i < valueCount; i++) {

            if (!editor.written[i]) {

                editor.abort();

                throw new IllegalStateException("Newly created entry didn't create value for " +

                        "index " + i);

            }

            if (!entry.getDirtyFile(i).exists()) {

                editor.abort();

                return;

            }

        }

    }



    for (int i = 0; i < valueCount; i++) {

        File dirty = entry.getDirtyFile(i);

        if (success) {//如果成功,清洗舊檔案

            if (dirty.exists()) {

                File clean = entry.getCleanFile(i);

                dirty.renameTo(clean);//重命名檔案

                long oldLength = entry.lengths[i];

                long newLength = clean.length();

                entry.lengths[i] = newLength;//更新檔案長度

                size = size - oldLength + newLength;

            }

        } else {

            deleteIfExists(dirty);//失敗就删除dirtyFile

        }

    }

//更新日志檔案

    redundantOpCount++;

    entry.currentEditor = null;

    if (entry.readable | success) {

        entry.readable = true;

        journalWriter.write(CLEAN + " " + entry.diskKey + " " + EXPIRY_PREFIX + entry

                .expiryTimestamp + entry.getLengths() + '\n');

        if (success) {

            entry.sequenceNumber = nextSequenceNumber++;

        }

    } else {

        lruEntries.remove(entry.diskKey);

        journalWriter.write(DELETE + " " + entry.diskKey + '\n');

    }

    journalWriter.flush();



    if (size > maxSize || journalRebuildRequired()) {

        executorService.submit(cleanupCallable);

    }

}
           
2.5.2.5下載下傳成功送出editor.commit();
/**

     * Commits this edit so it is visible to readers.  This releases the

     * edit lock so another edit may be started on the same key.

     */

    public void commit() throws IOException {

        if (hasErrors) {

            completeEdit(this, false);

            removeByDiskKey(entry.diskKey); // The previous entry is stale.

        } else {

            completeEdit(this, true);

        }

        committed = true;

    }







/**

 * Drops the entry for {@code diskKey} if it exists and can be removed. Entries

 * actively being edited cannot be removed.

 *

 * @return true if an entry was removed.

 * 删除clean檔案

 */

private synchronized boolean removeByDiskKey(String diskKey) throws IOException {

    checkNotClosed();

    Entry entry = lruEntries.get(diskKey);

    if (entry == null || entry.currentEditor != null) {

        return false;

    }



    for (int i = 0; i < valueCount; i++) {//删除clean檔案

        File file = entry.getCleanFile(i);

        if (file.exists() && !file.delete()) {

            throw new IOException("failed to delete " + file);

        }

        size -= entry.lengths[i];

        entry.lengths[i] = 0;

    }



    redundantOpCount++;

    journalWriter.append(DELETE + " " + diskKey + '\n');

    lruEntries.remove(diskKey);



    if (journalRebuildRequired()) {

        executorService.submit(cleanupCallable);

    }



    return true;

}
           
2.5.2.6再次擷取snapshot = mDiskLruCache.get(uri);見2.5.1.1,此次snapshot對象已經存在
2.5.2.7根據下載下傳的資源構造bitmap = decodeBitmapMeta(bitmapMeta, config);
/**

 * 通過BitmapMeta對象構造Bitmap

 * 1.如果BitmapMeta對象輸入流不為空,以輸入流擷取Bitmap

 * 2.如果BitmapMeta對象byte資料不為空,以此資料擷取Bitmap

 */

private Bitmap decodeBitmapMeta(BitmapMeta bitmapMeta, BitmapDisplayConfig config) throws

        IOException {

    if (bitmapMeta == null) return null;

    Bitmap bitmap = null;

    if (bitmapMeta.inputStream != null) {

        if (config == null || config.isShowOriginal()) {

            bitmap = BitmapDecoder.decodeFileDescriptor(bitmapMeta.inputStream.getFD());

        } else {

            bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(bitmapMeta.inputStream

                    .getFD(), config.getBitmapMaxSize(), config.getBitmapConfig());

        }

    } else if (bitmapMeta.data != null) {

        if (config == null || config.isShowOriginal()) {

            bitmap = BitmapDecoder.decodeByteArray(bitmapMeta.data);

        } else {

            bitmap = BitmapDecoder.decodeSampledBitmapFromByteArray(bitmapMeta.data, config

                    .getBitmapMaxSize(), config.getBitmapConfig());

        }

    }

    return bitmap;

}
           
2.5.2.8 如果下載下傳到磁盤緩存為null再去下載下傳到記憶體緩存中bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri,outputStream, task);見2.5.2.3
2.5.2.9 處理下載下傳的資源構造Bitmap,bitmap = decodeBitmapMeta(bitmapMeta, config);見2.5.2.7
2.5.2.10 如果下載下傳成功,先去處理圖檔旋轉問題;見2.5.1.4。同時将Bitmap添加到記憶體緩存中;見2.5.1.5

2.5.3 背景任務執行結束,處理回調onPostExecute

//Bitmap加載成功回調

    @Override

    protected void onPostExecute(Bitmap bitmap) {

        final T container = this.getTargetContainer();

        if (container != null) {

            if (bitmap != null) {

                callBack.onLoadCompleted(container, this.uri, bitmap, displayConfig, from);

            } else {

                callBack.onLoadFailed(container, this.uri, displayConfig

                        .getLoadFailedDrawable());

            }

        }

    }
           

至此BitmapLoadTask完成。

二 緻敬原作者,謝謝作者辛苦付出,代碼受益匪淺。

傳送門

三 其他講解:

架構講解:傳送門

四 源碼注釋

源碼詳細注釋:傳送門