Glide圖片載入過程(簡)
調研版本為4.7.1
為了更加簡單的理解,會將函式程式碼簡化,詳細程式碼請自行照原始碼對比
Glide用法
Glide.with(...).load(...).into(...)
我們從into方法開始探尋之旅
開啟載入執行緒
RequestBuilder.class public Target into() { Request request = buildRequest(...); requestTracker.runRequest(request); }
requestTracker是RequestTracker物件
RequestTracker.class public void runRequest(Request request) { request.begin(); }
request是SingleRequest物件
SingleRequest.class public void begin() { onSizeReady(...); } public void onSizeReady(...) { engine.load(...) }
engine是Engine類的物件
Engine.class void load(...) { engineJob.start(...) }
engineJob是EngineJob類的物件
EngineJob.class public void start(DecodeJob job) { GlideExecutor executor = ... //建立執行緒池 executor.execute(job); //job是一個runnable物件,此語句開啟執行緒 }
獲取資源過程
上面我們知道了開啟執行緒的過程,這個執行緒主要就是獲取二進位制流並且編碼成資原始檔,如Bitmap、Drawable等用來在Android中顯示。
DecodeJob.class //執行緒從run方法開始 public void run() { runWrapped(); } private void runWrapped() { runGenerators(); } private void runGenerators() { currentGenerator = getNextGenerator(); //依次呼叫generator的startNext方法 //startNext返回值表示是否可以獲得圖片的二進位制流 //如果generator能獲取圖片資料,那麼就不需要下級的generator去嘗試獲取圖片資料 while (!currentGenerator.startNext()) { ... currentGenerator = getNextGenerator(); } } //第一次呼叫該方法返回ResourceCacheGenerator //第二次返回DataCacheGenerator //第三次返回SourceGenerator(本地沒有快取時使用,下載資料並放入快取) private DataFetcherGenerator getNextGenerator() { switch (stage) { case RESOURCE_CACHE: return new ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: return new DataCacheGenerator(decodeHelper, this); case SOURCE: return new SourceGenerator(decodeHelper, this); } }
本例是一個imageView載入網路圖,ResourceCacheGenerator的startNext為false,這樣會呼叫DataCacheGenerator的startNext方法。
DataCacheGenerator.class //startNext方法會通過迴圈所有的modelLoader分別呼叫buildLoadData方法 //如果所有modelLoader獲取的loadData都為空那麼就返回false //這就意味著Glide將呼叫下一個Generator的startNext方法 //也就是SourceGenerator的startNext方法 public boolean startNext() { Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); File cacheFile = helper.getDiskCache().get(originalKey); boolean started = false; while (!started && hasNextModelLoader()) { //我們自定義的ModelLoader就可以在這派上用場了 ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++); ModelLoader.LoadData loadData = modelLoader.buildLoadData(...); if (loadData != null) { started = true; //本例中ByteBufferFileLoader獲取的loadData不為空 //本例中loadData.fetcher是ByteBufferFetcher的物件 loadData.fetcher.loadData(this); } } return started; }
ByteBufferFetcher.class public void loadData(DataFetcher.DataCallback<? super ByteBuffer> callback) { ByteBuffer result; try { result = ByteBufferUtil.fromFile(file); } catch (IOException e) { callback.onLoadFailed(e); return; } callback.onDataReady(result); }
這裡的callback是DataCacheGenerator物件,該類實現了DataFetcher.DataCallback介面,所以此處回撥到DataCacheGenerator類中的onDataReady方法
DataCacheGenerator.class public void onDataReady(Object data) { cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey); }
cb是DecodeJob類物件
DecodeJob.class public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { //將二進位制流解碼成資原始檔 decodeFromRetrievedData(); } } private void decodeFromRetrievedData() { Resource<R> resource = null; resource = decodeFromData(currentFetcher, currentData, currentDataSource); if (resource != null) { notifyEncodeAndRelease(resource, currentDataSource); } }
notifyEncodeAndRelease方法裡主要是使用Handler回到主執行緒呼叫各種回撥以及釋放資源。至此,載入過程就分析完畢啦。
在上面我們已經完整的走過了一個載入流程,但是Glide快取的檔案是什麼形式的呢?如果一個圖片的url被Glide載入快取過,我們是不是可以通過該url獲取快取檔案呢?獲取的快取檔案是不是還是.jpg、.png等圖片形式的可以直接從手機提取到電腦呢?如果你有這些疑問,那就繼續往下看吧~
Glide快取機制
Glide預設快取在 data/data/包名/cache/image_manager_disk_cache 資料夾下,並且快取檔案的檔名根本不能直觀上和請求的圖片URL所匹配:
類似這種 021db417987c5c446d2042037c78e9d9c127432d5a8cdd2b58cd2a702fc917b3.0
,字尾還是 .0 。是不是有點頭大,讓我們去看看檔名的生成規則吧。

快取資料夾
Glide使用DiskLruCacheWrapper類進行快取檔案操作,我們主要看put和get方法,一個用於取快取,一個用於存快取。
DiskLruCacheWrapper.class public File get(Key key) { String safeKey = safeKeyGenerator.getSafeKey(key); File result = null; final DiskLruCache.Value value = getDiskCache().get(safeKey); result = value.getFile(0); return result; } public void put(Key key, DiskCache.Writer writer) { String safeKey = safeKeyGenerator.getSafeKey(key); DiskLruCache diskCache = getDiskCache(); DiskLruCache.Editor editor = diskCache.edit(safeKey); File file = editor.getFile(0); if (writer.write(file)) { editor.commit(); } }
safeKeyGenerator是SafeKeyGenerator類的物件,其getSafeKey方法主要通過圖片url獲取快取檔名,getSafeKey需要傳入一個Key物件,Key物件就包括了要載入的url。
SafeKeyGenerator.class public String getSafeKey(Key key) { return calculateHexStringDigest(key); } private String calculateHexStringDigest(Key key) { return Util.sha256BytesToHex(container.messageDigest.digest()); }
這裡打個斷點讓大家看看Key裡都有什麼成員變數

Key.class
calculateHexStringDigest方法也很簡單,就是對該URL使用SHA256加密演算法加密
為了讓大家理解,這裡貼兩張圖,第一張圖是將圖片地址字串進行加密計算結果圖,第二張圖是手機快取資料夾圖片


那麼字尾 .0 是怎麼生成的呢?
我們可以看到在呼叫DiskLruCacheWrapper的put函式儲存快取檔案時有一句程式碼
DiskLruCache.Editor editor = diskCache.edit(safeKey);
其中diskCache是DiskLruCache物件
DiskLruCache.class private DiskLruCache.Editor edit(String key) { DiskLruCache.Entry entry = lruEntries.get(key); if (entry == null) { entry = new DiskLruCache.Entry(key); lruEntries.put(key, entry); } }
注意了,這裡的lruEntries是一個LinkedHashMap,裡面存的就是圖片url和DiskLruCache.Entry,其中DiskLruCache.Entry就存有快取檔名!
可以看到Glide會優先從該Map中取,如果沒有找到則自己建立DiskLruCache.Entry然後再放入Map
我們可以直接看DiskLruCache.Entry的建構函式
private Entry(String key) { cleanFiles = new File[valueCount]; StringBuilder fileBuilder = new StringBuilder(key).append('.'); for (int i = 0; i < valueCount; i++) { fileBuilder.append(i); cleanFiles[i] = new File(directory, fileBuilder.toString()); } }
這裡cleanFiles就存放著快取檔案,其中valueCount=1,至於valueCount為什麼是1是因為呼叫DiskLruCache的靜態方法open public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
時傳的valueCount引數為1
那麼既然valueCount為1,上面程式碼應該可以簡化了吧?
private Entry(String key) { cleanFiles = new File[1]; StringBuilder fileBuilder = new StringBuilder(key).append('.'); fileBuilder.append(0); cleanFiles[0] = new File(directory, fileBuilder.toString()); }
所以看了這麼多,想到怎麼用url獲取本地圖片了嘛?
另外,試試把 xxx.0 換成 xxx.png ~