Glide 系列四:Glide 的網路模組到底在哪裡?
Glide 系列四:Glide 的網路模組到底在哪裡?
本文概述:
-
傳言
- 曾有一位開發者花費一週時間,找不到Glide 的網路模組在哪裡;
- Glide 相較於RxJava,其整體設計思路並不複雜,但是使用了大量的介面,導致程式碼非常繞;而RxJava 程式碼較為簡單,但是設計思路非常複雜;
-
本文以Glide 原始碼流程詳細分析了,網路模組到底在哪裡;
Glide 使用
- 基本使用
Glide.with(this).load(URL).into(imageView);
- 拆分使用過程
RequestManager requstManger = Glide.with(this);
RequestBuilder requestBuilder = requstManger.load(URL);
requestBuilder.into(imageView);
使用Glide ,出現記憶體洩漏,是為了什麼?
-
作用域問題:
- 儘量在with的時候,傳入有生命週期的作用域(非Application作用域),儘量避免使用了Application作用域,因為Application作用域不會對頁面繫結生命週期機制,就回收不及時釋放操作等....
原始碼分析:
Glide.with:
-
最終返回RequestManager 物件
- 關鍵程式碼:
public RequestManager get(@NonNull FragmentActivity activity) { if (Util.isOnBackgroundThread()) { // Application 作用域 } else { // 非Application 作用域 } }
requestManager.load(URL )
- 最終返回RequestBuilder
ImageView 是怎麼顯示的?
requestBuilder.into(ImageView)
核心問題:
- 知道圖片是怎麼在imageView 控制元件上顯示的?
整體流程分析:Glide 的網路訪問模組在哪裡?
編寫說明:
- 按照into 原始碼執行順序,分為主線與支線
- 跳過了許多細節,因為Glide 的原始碼實在是太深了;
在MainActivity 中
- 從requestBuilder.into(imageView); 通過into,進入RequestBuilder.into()
在RequestBuilder.into()
-
函式簽名展示:
@NonNull public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
-
檢查是否為主執行緒,控制元件是否為空 [支線]
Util.assertMainThread(); Preconditions.checkNotNull(view);
-
根據控制元件屬性(scaleType)定義,決定requestOptions 走哪裡去 [支線]
- 使用Switch-case 實現,沒有設定控制元件的scaleType 屬性,走預設流程:case FIT_END
- 並且在這段程式碼中使用了clone() 操作,這是原型設計模式
switch (view.getScaleType()) { case FIT_END://預設流程 requestOptions = requestOptions.clone().optionalFitCenter(); break;
-
構建ImageViewTarget (顯示圖片) [主線]
-
後續原始碼一定會回撥到這個地方 ,伏筆一
- 因為這個ImageViewTarget 是用來顯示圖片的 ;
-
程式碼展示
//構建出ImageViewTarget return into( glideContext.buildImageViewTarget(view, transcodeClass), ) ;
-
-
從return into,通過into 再次進入RequestBuilder.into()
在RequestBuilder.into()
-
函式簽名展示
private <Y extends Target<TranscodeType>> Y into(
-
存在Request 介面:為了擴充套件性,需要面向於高層 [主線]
- Request 介面的子類:SingleRequest
//原始碼: Request request = buildRequest(target, targetListener, options, callbackExecutor); //介面 public interface Request { //伏筆二: Request request = new SingleRequest
-
處理上一個請求 [支線]
- 當新的請求來了,會去處理上一個請求
-
從requestManager.track(target, request); 通過track 進入RequestManager.track()
在RequestManager.track()
-
函式簽名展示:
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
-
從requestTracker.runRequest(request); 通過runRequest 進入RequestTracker.runRequest
在RequestTracker.runRequest
-
函式簽名展示:
public void runRequest(@NonNull Request request) {
-
將當前請求新增到執行佇列中: [主線]
requests.add(request);
-
當暫停標誌位為false ,執行請求 [主線]
if (!isPaused) { request.begin(); }
-
通過begin 進入SingleRequest.begin()
-
-
當暫停標誌位為true ,將請求新增到等待佇列中 [主線]
else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); }
在SingleRequest.begin()
-
函式簽名展示:
@Override public void begin() {
- 因為我們在之前分析過了,Request 的子類為SingleRequest 所以可以大膽向下走
- 注意:在閱讀原始碼時,一定要去埋下伏筆,不然原始碼根本不好找,遇到介面實現類不清楚,那麼就往前走
-
begin() 中的關鍵函式:當用戶沒有設定寬高時,會去測量寬高並回調到此處
- 從onSizeReady(overrideWidth, overrideHeight); 通過onSizeReady 進入SingleRequest.onSizeReady()
在SingleRequest.onSizeReady()
-
函式簽名展示:
@Override public void onSizeReady(int width, int height) {
-
加了鎖的:多執行緒併發
private final Object requestLock; synchronized (requestLock) {
-
通過引擎載入:
loadStatus = engine.load(
-
從engine.load( 通過load 進入Engine.load()
在Engine.load() [主線]
-
存在Enginekey
EngineKey key = keyFactory.buildKey(
-
存在快取機制
EngineResource<?> memoryResource; synchronized (this) { memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
-
補充圖片訪問流程:命中快取後直接返回
- 優先查詢活動快取(執行時快取)
- 再查詢記憶體快取(執行時快取)
- 再查詢磁碟快取
-
訪問依據,就是這個key
-
這個key 是怎麼構造的?
- 根據圖片的寬、高、簽名為每一張圖片建立一把唯一的key
- 構造程式碼
EngineKey key = keyFactory.buildKey( model, signature, width, height, transformations, resourceClass, transcodeClass, options);
-
-
通過loadFromMemory 進入Engine.loadFromMemory()
Engine.loadFromMemory() 快取機制
-
函式簽名:
@Nullable private EngineResource<?> loadFromMemory(
-
優先查詢活動快取(執行時一級快取):若命中,則直接返回
EngineResource<?> active = loadFromActiveResources(key); if (active != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return active; }
-
再查詢記憶體快取(執行時二級快取):若命中,則直接返回
EngineResource<?> cached = loadFromCache(key); if (cached != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return cached; }
-
備註:
-
當任一執行時快取命中時,會回退至Engine.load() 執行
//命中快取,回撥給外界去顯示圖片 cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
-
當執行時快取無一命中,返回null
return null;
-
此時在Engine.load() 中則會執行waitForExistingOrStartNewJob
if (memoryResource == null) { return waitForExistingOrStartNewJob(
-
通過waitForExistingOrStartNewJob 進入Engine.waitForExistingOrStartNewJob
-
-
-
為什麼要設計兩級執行時快取
-
記憶體快取由LRUcache 管理
-
業務場景:maxSize = 3
- 當新增資料時,超過maxSize ,就會移除掉記憶體快取中最長未使用的圖片,如果這張圖片正在被Activity 使用,就會出現崩潰;
-
為了解決這個崩潰:設計了活動快取
- 將所有正在顯示的圖片,放到活動快取中;也就是Activity 真正顯示的圖片必須訪問活動快取
-
在Engine.waitForExistingOrStartNewJob()
-
查詢有無正在執行的任務
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); }
-
EngineJob :執行緒池大管家,在Glide 4.11 後使用工廠設計模式構建
-
decodeJob :代表具體任務(Runnable ),在Glide 4.11 後使用工廠設計模式構建
-
-
將decodeJob (具體任務)交給engineJob (執行緒池)執行
engineJob.start(decodeJob);
-
具體執行邏輯
public synchronized void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }
-
-
decodeJob (具體任務) 具體是怎麼執行的?
- 通過DecodeJob 跳轉至DecodeJob.run()
在DecodeJob.run()
-
程式碼展示:
public void run() { …… runWrapped(); …… }
-
進入DecodeJob.runWrapped();
在DecodeJob.runWrapped();
-
函式簽名展示:
private void runWrapped() {
-
拿到快取策略機制:通過switch-case
-
沒有配置就走預設
switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE);
- 預設快取策略機制
currentGenerator = getNextGenerator(); //返回這個:伏筆二 new SourceGenerator(decodeHelper, this);
-
這樣走的
private DataFetcherGenerator getNextGenerator() { switch (stage) { …… case SOURCE://走這個 return new SourceGenerator(decodeHelper, this);
-
執行獲取到的快取策略:runGenerators(); 進入DecodeJob.runGenerators();
-
在DecodeJob.runGenerators();
-
函式簽名:
private void runGenerators() {
-
注意,此時我們走的是預設的快取流程
while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) {
-
currentGenerator 指的是SourceGenerator ,因為startNext 是介面
-
-
此時進入SourceGenerator.startNext()
在SourceGenerator.startNext()
-
關鍵程式碼:helper.getLoadData()
while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++);
-
通過getLoadData() 進入DecodeHelper.getLoadData()
在DecodeHelper.getLoadData()
-
結論:最終返回根據使用者傳入引數,拿到的真實物件
- 比如說,我們傳入URL ,那麼getLoadData 拿到HttpUrlFetcher 物件
-
關鍵程式碼:modelLoader.buildLoadData
LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
-
通過buildLoadData 進入ModelLoader 介面的buildLoadData 介面
-
介面展示:歇逼了,找不到實現類啊,
-
-
這個實現類在哪裡:在Glide 建構函式中,有個註冊機,裡面就給我設定好了的
//註冊機:Glide.java 中的
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
//我們在使用Glide 時,第二個引數是String 型別的URL ,註冊機幫助進行了適配
-
有了註冊機,那麼就找到上面那個介面的實現類了
-
註冊機展示:第一個引數為使用者傳入的實參(就是我們自己的寫入的URL 之類的),在執行的時候是通過第三個引數,去構造第二個引數型別的資料流(註冊機的本質)
-
進入HttpGlideUrlLoader.buildLoadData()
在HttpGlideUrlLoader.buildLoadData()
-
關鍵程式碼:Glide 的網路訪問模組
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
-
總結:
- HttpGlideUrlLoader :只是裝飾物件
- HttpUrlFetcher :才是真實物件
-
此時,向上回退至SourceGenerator.startNext()
回退至SourceGenerator.startNext()
-
此時拿到了HttpUrlFetcher 物件
loadData = helper.getLoadData().get(loadDataListIndex++);
-
將拿到的HttpUrlFetcher 作為引數傳入startNextLoad(loadData);
- 此時進入SourceGenerator.startNextLoad();
在SourceGenerator.startNextLoad();
-
準備執行
private void startNextLoad(final LoadData<?> toStart) { loadData.fetcher.loadData(
-
進入loadData :發現這個居然又是介面!!!
- 幸好知道它的實現類是HttpUrlFetcher
-
此時,進入HttpUrlFetcher.loadData()
在HttpUrlFetcher.loadData()
-
關鍵程式碼:
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
-
通過loadDataWithRedirects 進入HttpUrlFetcher.loadDataWithRedirects ()
在HttpUrlFetcher.loadDataWithRedirects ()
-
這裡就是Glide 的網路訪問部分了,最終是會返回InputStream
-
總結:Glide 的原始碼十分繞,RxJava 是編寫思路繞,程式碼較為簡單;而Glide 構建思路比如輕鬆,但是程式碼邏輯是非繞;
使用Glide為什麼要加入網路許可權?
- 等待佇列/執行佇列
- 執行Request ---> 活動快取 --->記憶體快取 ---> jobs.get檢測執行的任務有沒有執行完成 ---> HttpUrlFetcher.HttpURLConnection
- AMS核心原理初探一:SystemServer 程序
- Glide 系列四:Glide 的網路模組到底在哪裡?
- 安卓虛擬機器系列二:深入分析ClassLoader 機制
- 安卓虛擬機器系列一:基於棧 OR 基於暫存器、不同android 版本對程式的處理
- RxJava 系列九:執行緒切換的原始碼分析
- RxJava 系列四: doOnNext 的運用
- RxJava操作符(RxBinding與flatMap)的應用場景及其使用細節
- 在Android 中如何為控制元件新增監聽器(三種方式)
- Android 常見佈局全解之線性佈局
- 如何建立並使用一個協程:對比runBlocking、GlobalScope與自定義協程作用域
- Kotlin 中函式引數為函式:以模擬資料庫為例
- Kotlin協程初探(一)
- Navigation使用重顧
- DataBinding原始碼初探
- LiveData粘性資料自定義
- DataBinding實現雙向繫結,達到資料驅動UI效果(Java版本)