code小生 一個(gè)專注大前端領(lǐng)域的技術(shù)平臺(tái)公眾號(hào)回復(fù)
前言1. Glide 基本用法接下來的講解將基于 Glide 目前的最新版本 4.11,。 Glide 的使用特別簡單,首先添加依賴,。 然后調(diào)用下面這三個(gè)方法,。
2. 內(nèi)容概覽Glide 加載圖片大致可分為三個(gè)步驟,。
接下來會(huì)以這三個(gè)步驟為基礎(chǔ)來展開 Glide 的圖片加載流程,,下面是每個(gè)大節(jié)講解的內(nèi)容。 1. 四步啟動(dòng)解碼任務(wù)第一大節(jié)將會(huì)講解從我們調(diào)用 into() 方法到啟動(dòng) DecodeJob 的 run() 方法的過程中,,Glide 做了哪些事情,,包含了 Request、Target ,、 Engine 和 DecodeJob 等內(nèi)容,。 2. 六步加載圖片第二大節(jié)會(huì)講解當(dāng) DecodeJob 獲取到圖片數(shù)據(jù)后,會(huì)怎么處理圖片數(shù)據(jù),,也就是解碼,、加載圖片和編碼的過程,包括 ModelLoader,、ResourceDecoder,、Transformation、ResourceTranscoder 以及 ResourceEncoder 的實(shí)現(xiàn),。 3. Glide 緩存原理這里會(huì)講 Glide 的圖片緩存相關(guān)的實(shí)現(xiàn),,包括內(nèi)存緩存、磁盤緩存,、BitmapPool 以及 ArrayPool 等內(nèi)容,。 4. Glide 初始化流程與配置這一節(jié)會(huì)講解 Glide 的初始化流程,包括 Glide 調(diào)用配置的方式,、AppGlideModule 的兩個(gè)方法中用到的 Registry 和 GlideBuilder 在 Glide 中的作用,。 5. Glide 圖片加載選項(xiàng)Glide 開放了非常多的圖片加載選項(xiàng),我們不一定全都用得上,,但是了解這些選項(xiàng),,可以讓我們?cè)谛枰臅r(shí)候能調(diào)用對(duì)應(yīng)的選項(xiàng),不用再自己實(shí)現(xiàn)一遍,。 1. 四步啟動(dòng)解碼任務(wù)這一節(jié)我們先來看下從 into() 到啟動(dòng) DecodeJob 的過程中涉及哪些對(duì)象,。 從我們調(diào)用 into() 方法加載圖片到啟動(dòng)解碼任務(wù) DecodeJob,大致分為 4 個(gè)步驟,,涉及下面 4 個(gè)對(duì)象,。
下面我們圍繞這 4 個(gè)對(duì)象看看 Glide 的解碼任務(wù)的啟動(dòng)流程。 1.1 Request1.1.1 請(qǐng)求構(gòu)建器 RequestBuilder1. into()對(duì)于我們發(fā)起的圖片加載請(qǐng)求,,Glide 會(huì)把這個(gè)請(qǐng)求封裝為 Request,,而 RequestBuilder 就是 Request 的構(gòu)建器,。 當(dāng)我們用 into() 方法加載圖片時(shí),調(diào)用的其實(shí)是 RequestBuilder 的 into() 方法,,這個(gè)方法會(huì)創(chuàng)建一個(gè) Request ,,并把 Request 傳給請(qǐng)求管理器。 2. ModelGlide 會(huì)把我們?cè)?load() 中傳入的圖片來源數(shù)據(jù)封裝為 Model ,,而這個(gè) Model 具體就是 RequestBuilder 中的 model 字段,,類型為 Object 。 3. 加載選項(xiàng)RequestBuilder 繼承了 BaseRequestOptions 抽象類,,我們平時(shí)用的 centerCrop() 等方法大部分都是 BaseRequestOptions 的方法,,關(guān)于圖片加載選項(xiàng)在后面會(huì)講到。 1.1.2 請(qǐng)求管理器 RequestManagerRequestManager 有下面幾個(gè)特點(diǎn),。
1. 綁定生命周期在 Glide 中,,一個(gè) Context 對(duì)應(yīng)一個(gè) RequestManager,當(dāng)我們調(diào)用 with() 方法時(shí),,RequestManager 會(huì)用對(duì)應(yīng)的 Context 創(chuàng)建一個(gè) RequestManagerFragment ,。 RequestManagerFragment 是一個(gè)無布局的 Fragment,主要是用來做生命周期關(guān)聯(lián)的,,當(dāng)這個(gè) Fragment 感知到 Activity 的生命周期發(fā)生變化時(shí),,就會(huì)告訴請(qǐng)求管理器,讓它去做暫停請(qǐng)求,、繼續(xù)請(qǐng)求和取消請(qǐng)求等操作,。 如果我們用的是 ApplicationContext 加載某張圖片,那就意味著這次圖片加載操作的生命周期是與應(yīng)用的生命周期綁定的,。 2. 監(jiān)聽網(wǎng)絡(luò)狀態(tài)RequestManager 中有一個(gè)網(wǎng)絡(luò)連接監(jiān)聽器 RequestManagerConnectivityListener ,,它實(shí)現(xiàn)了ConnectivityListener 接口,每次網(wǎng)絡(luò)狀態(tài)切換時(shí),,RequestManager 就會(huì)重啟所有的圖片加載請(qǐng)求,。 3. 創(chuàng)建請(qǐng)求構(gòu)建器我們?cè)诩虞d圖片時(shí)調(diào)用的 load() 方法是 RequestManager 的方法,調(diào)用這個(gè)方法其實(shí)是創(chuàng)建了一個(gè)請(qǐng)求構(gòu)建器 RequestBuilder,,RequestManager 中有很多創(chuàng)建 RequestBuilder 的方法,,比如 asDrawable()、asBitmap() ,、asFile() 等,,這些方法對(duì)應(yīng)著不同泛型參數(shù)的 RequestBuilder 。 load() 方法支持下面這些類型的參數(shù),。
4. 啟動(dòng)請(qǐng)求RequestManager 的 track() 方法調(diào)用了目標(biāo)跟蹤器 TargetTracker 的 track() 方法,,還調(diào)用了請(qǐng)求跟蹤器 RequestTracker 的 runRequest() 方法 。
1.1.3 Request 的 6 種狀態(tài)前面講到的 Request 具體就是 SingleRequest ,SingleRequest 中有一個(gè) Status 枚舉類,,包含了請(qǐng)求的 6 種狀態(tài)。 1. 待運(yùn)行 PENDING當(dāng)我們通過 into() 創(chuàng)建了一個(gè) SingleRequest 后,,該 Request 就進(jìn)入了待運(yùn)行狀態(tài),。 2. 已清除 CLEARED每次我們用 into() 方法加載圖片時(shí),RequestManager 都會(huì)先看下我們傳入的 Target 是否有對(duì)應(yīng)的 Request ,,如果有的話就會(huì)調(diào)用該 Request 的 clear() 方法釋放資源,,這時(shí) Request 就進(jìn)入了已清除狀態(tài)。 3. 待測(cè)量 WAITING_FOR_SIZE當(dāng) RequestManager 調(diào)用 RequestTracker 的 runRequest() 方法后,,RequestTracker 就會(huì)調(diào)用 Request 的 begin() 方法,,這時(shí)請(qǐng)求就進(jìn)入了待測(cè)量狀態(tài),。 4. 運(yùn)行中 RUNNING在 SingleRequest 的 begin() 方法中,,調(diào)用了 Target 的 getSize() 方法獲取 ImageView 的尺寸,獲取到尺寸后,,SingleRequst 會(huì)調(diào)用 Engine 的 load() 方法啟動(dòng)圖片加載請(qǐng)求,,這時(shí) Request 就進(jìn)入了運(yùn)行中狀態(tài)。 5. 已完成 COMPLETE當(dāng) Engine 從內(nèi)存中加載到資源,,或者通過解碼任務(wù)加載到資源后,,就會(huì)調(diào)用 SingleRequest 的 onResourceReady() 方法,,這時(shí) Request 就進(jìn)入了已完成狀態(tài),。 6. 失敗 FAILED當(dāng)解碼任務(wù) DecodeJob 在處理圖片的過程中遇到異常時(shí),,就會(huì)調(diào)用 EngineJob 的 onLoadFailed() 方法,然后 EngineJob 會(huì)調(diào)用 SingleRequest 的 onLoadFailed() 方法,,這時(shí) SingleRequest 就進(jìn)入了失敗狀態(tài),。 1.1.4 三種占位圖我們?cè)诩虞d圖片時(shí),可以設(shè)置 placeholder,、error 和 fallback 三種占位圖,。
使用占位圖時(shí),要注意占位圖是不會(huì)使用 Transformation 進(jìn)行變換的,,如果你想弄個(gè)圓角或圓形的占位圖,,可以用 submit().get() 獲取對(duì)應(yīng)變換后的占位圖的 Drawable 對(duì)象,然后傳到對(duì)應(yīng)的占位圖設(shè)置方法中,。 1.1.5 Request 相關(guān)問題下面是幾個(gè)跟 Request 相關(guān)的問題,,看看你能不能答得上來。
1.2 Target當(dāng)我們調(diào)用 into() 方法,,傳入 ImageView 后,,Glide 會(huì)把 ImageView 轉(zhuǎn)化為 Target ,,下面我們來看下不同 Target 的作用。 1.2.1 ImageViewTarget1. SizeDeterminerImageViewTarget 繼承了 ViewTarget ,,在 ViewTarget 中有一個(gè)用來獲取尺寸的 SizeDeterminer ,,SizeDeterminer 的 getSize() 方法拿到的尺寸,,是把 ImageView 的內(nèi)邊距 padding() 去掉后的尺寸。 在 Glide 中,,寬高分為請(qǐng)求寬高和原始寬高 ,,而 SizeDeterminer 拿到的尺寸就是請(qǐng)求寬高,Glide 會(huì)根據(jù)請(qǐng)求寬高對(duì)圖片進(jìn)行縮放操作,,以減少不必要的內(nèi)存消耗,。 2. OnPreDrawListener當(dāng) Request 獲取 View 的尺寸失敗時(shí),ViewTarget 會(huì)通過 ViewTreeObserver 的 OnPreDrawListener 的回調(diào)來獲取 View 的尺寸,,然后再傳給 Request,。 3. setResource()ImageViewTarget 主要有 BitmapImageViewTarget 和 DrawableImageViewTarget 兩個(gè)子類,它們兩個(gè)的區(qū)別就在于它們的 setResource() 方法,。
1.2.2 RequestFutureTarget1. submit()FutureTarget 是一個(gè)實(shí)現(xiàn)了 Future 和 Target 接口的接口,,它只有一個(gè) RequestFutureTarget 子類 ,,當(dāng)我們用 submit() 方法獲取 Glide 加載好的圖片資源時(shí),,就是創(chuàng)建了一個(gè) RequestFutureTarget 。 2. WaiterRequestFutureTarget 是用 wait/notify 的方式來實(shí)現(xiàn)等待和通知的,,這兩個(gè)是 Object 的方法,,Request 中有一個(gè) Waiter ,當(dāng) DecodeJob 加載到圖片后,,RequestFutureTarget 就會(huì)讓 Waiter 發(fā)出通知,,這時(shí)我們的 get() 方法就能獲取到返回值了。 這就是為什么我們用 RequestFutureTarget 的 get() 方法獲取圖片時(shí),,要把這個(gè)操作放在子線程運(yùn)行,。 1.2.3 CustomTarget給不是 View 的 Target 加載圖片時(shí),Glide 都把它作為 CustomTarget ,。 1. PreloadTarget預(yù)加載 Target 。 當(dāng)我們調(diào)用 preload() 選項(xiàng)預(yù)加載圖片時(shí),,Glide 會(huì)把圖片交給 PreloadTarget 處理,,當(dāng) PreloadTarget 接收到圖片資源后,就會(huì)讓 RequestManager 把該請(qǐng)求的資源釋放掉,。 因?yàn)椴恍枰却Y源加載完成,,所以我們?cè)谟?preload() 預(yù)加載圖片時(shí),不用像 submit() 一樣在子線程中執(zhí)行,。 2. AppWidgetTarget桌面組件 Target ,。 當(dāng) AppWidgetTarget 接收到處理好的圖片資源后,會(huì)把它設(shè)置給 RemoteView ,,然后通過桌面組件管理器 AppWidgetManager 更新桌面組件,。 3. DelayTargetGifTarget。 這是加載 Gif 圖片時(shí)要用到的 Target ,,關(guān)于 Glide 加載 Gif 圖片的流程在后面會(huì)講到,。 4. NotificationTarget通知欄 Target 。 這個(gè) Target 有一個(gè) setBitmap 方法,,會(huì)把圖片設(shè)置給通知欄的 RemoteView ,,然后通過 NotificationManager 更新通知欄中的通知。 1.2.4 Target 相關(guān)問題
1.3 Engine下面我們來看一些與 Engine 相關(guān)的實(shí)現(xiàn),。
1.3.1 Engine 的作用Engine 是 Glide 的圖片加載引擎,,是 Glide 中非常重要的一個(gè)類,,下面我們來看下 Engine 的作用。 1. load()前面講到了當(dāng)我們調(diào)用 into() 方法時(shí),,就是間接調(diào)用了 Request.begin() 方法,,而 Request 的 begin() 方法又調(diào)用了 Engine 的 load() 方法。 在 load() 方法中,,Engine 會(huì)先用 EngineKeyFactory 創(chuàng)建資源標(biāo)識(shí)符 Key,,然后用這個(gè) Key 去內(nèi)存緩存中加載資源。 如果從內(nèi)存中找到了資源,,Engine 就會(huì)直接把資源回傳給 Resource,,如果沒有加載到資源,Engine 就會(huì)創(chuàng)建并啟動(dòng)新的 EngineJob 和解碼任務(wù) DecodeJob,。 2. EngineKeyFactoryEngineKeyFactory 是 Engine 中一個(gè)負(fù)責(zé)生產(chǎn) EngineKey 的工廠,,EngineKey 是引擎任務(wù)資源標(biāo)識(shí)符,關(guān)于什么是 Key 后面進(jìn)一步講,。 在 Engine 啟動(dòng)新的任務(wù)加載圖片前,,會(huì)先通過 EngineKeyFactory 創(chuàng)建一個(gè) EngineKey,然后讓 DecodeJob 把資源與 EngineKey 進(jìn)行綁定,,這里說的綁定,,其實(shí)就是把 model 放到 EngineKey 中。 3. 回收資源Engine 中有一個(gè)資源回收器 ResourceRecycler ,,Resource 接口中有一個(gè) recycle() 方法,,關(guān)于 Resource 我們后面再講。 這里只要知道,,當(dāng) SingleRequest 被清除,,比如在 into() 方法中發(fā)現(xiàn) Target 已經(jīng)有對(duì)應(yīng)的 Request 時(shí),Request 就會(huì)讓 Engine 釋放資源,,具體做釋放資源操作的就是 ResourceRecycler,。 4. 磁盤緩存提供器LazyDiskCacheProvider 是 Engine 中的一個(gè)靜態(tài)內(nèi)部類,是磁盤緩存 DiskCache 的提供器,,DiskCache 是一個(gè)接口,,關(guān)于 DiskCache 的實(shí)現(xiàn)我們后面再講。 5. 啟動(dòng)新的解碼任務(wù)當(dāng) Engine 從內(nèi)存中找不到對(duì)應(yīng)的 Key 的資源時(shí),,就會(huì)啟動(dòng)新的解碼任務(wù),。 Engine 會(huì)用加載任務(wù)工廠 EngineJobFactory 構(gòu)建一個(gè)加載任務(wù) EngineJob,然后再構(gòu)建一個(gè)解碼任務(wù) DecodeJob,。 EngineJob 這個(gè)名字看起來很霸氣,,但是實(shí)際上它并沒有做什么事情,它只是 Engine 與 DecodeJob 之間溝通的橋梁。 當(dāng)構(gòu)建了 EngineJob 和 DecodeJob 后,,Engine 就會(huì)把 DecodeJob 提交到線程池 GlideExecutor 中,。 1.3.2 Key前面講到了 Engine 會(huì)通過 EngineKeyFactory 創(chuàng)建資源標(biāo)識(shí)符 Key ,那什么是 Key ,? Key 是 Glide 中的一個(gè)接口,,是圖片資源的標(biāo)識(shí)符。 1. 避免比較有誤Glide 的內(nèi)存緩存和磁盤緩存用的都是 Glide 自己實(shí)現(xiàn)的 LruCache,,LruCache 也就是最近最少使用緩存算法(Least Recently Used),,LruCache 中有一個(gè) LinkedHashMap ,這個(gè) HashMap 的 Key 就是 Key 接口,,而 Value 則是 Resource 接口,。 在用對(duì)象作為 HashMap 的 Key 時(shí),要重寫 equals() 和 hashCode() 方法,。 如果不重寫這兩個(gè)方法,,那么當(dāng)兩個(gè) Key 的內(nèi)存地址不同,但是實(shí)際代表的資源相同時(shí),,使用父類 Object的 hasCode() 直接用內(nèi)存地址做比較,,那么結(jié)果會(huì)是不相等。 此外 Object 的 equals() 方法也是拿內(nèi)存地址作比較,,所以也要重寫。 比如下面就是 ResourceCacheKey 的 equals() 判斷邏輯,。 2. Key 實(shí)現(xiàn)類下面是幾個(gè)實(shí)現(xiàn)了 Key 接口的類,。
1.3.3 Resource前面講到了 Engine 會(huì)通過 ResourceRecycler 來回收資源,,而 ResourceRecycler 調(diào)用了 Resource 的 recycle() 方法。 可能你想起來 Bitmap 就有一個(gè)可以回收?qǐng)D片內(nèi)存的 recycle() 方法,,沒錯(cuò),,Glide 回收 Bitmap 的方式就是用的 Bitmap 自帶的 recycle() 方法,但是這個(gè)過程又比這復(fù)雜一些,。 Resource 是一個(gè)接口,,其中一個(gè)實(shí)現(xiàn)類是 BitmapResource ,,也就是位圖資源,比如網(wǎng)絡(luò)圖片就會(huì)轉(zhuǎn)化為 BitmapResource,。 在 BitmapResource 中有一個(gè)位圖池 BitmapPool,,這是 Glide 用來復(fù)用 Bitmap 的一個(gè)接口,具體的實(shí)現(xiàn)類是 LruBitmapPool ,。 在 BitmapResource 的 recycle() 方法中,,會(huì)把對(duì)應(yīng)的 Bitmap 通過 put() 方法放到 BitmapPool 中,關(guān)于 BitmapPool 在講 Glide 緩存原理時(shí)會(huì)進(jìn)一步講,。 1.3.4 Engine 相關(guān)問題
1.4 DecodeJob前面講到了 Engine 在緩存中找不到資源時(shí),,就會(huì)創(chuàng)建新的加載任務(wù) EngineJob 和新的解碼任務(wù) DecodeJob ,,然后讓 EngineJob 啟動(dòng) DecodeJob。 DecodeJob 實(shí)現(xiàn)了 Runnable 接口,,EngineJob 啟動(dòng) DecodeJob 的方式就是把它提交給 GlideExecutor,,如果我們沒有調(diào)整磁盤緩存策略的話,那默認(rèn)用的就是 diskCacheExecutor ,,關(guān)于 GlideExecutor 在第 4 大節(jié)會(huì)講,,下面我們先看下 DecodeJob 的實(shí)現(xiàn)。 1.4.1 runWrapped()DecodeJob 的 run() 方法只是對(duì) runWrapped() 可能遇到的異常進(jìn)行了捕獲,,而 runWrapped() 方法會(huì)根據(jù)不同的運(yùn)行理由 RunReason 運(yùn)行不同的數(shù)據(jù)生成器,。 1. 三種運(yùn)行理由runWrapped() 會(huì)根據(jù)下面三種運(yùn)行理由來執(zhí)行解碼任務(wù)。
2. 初始化當(dāng)運(yùn)行理由為默認(rèn)狀態(tài) INITIALIZE 時(shí),DecodeJob 會(huì)從磁盤中獲取圖片數(shù)據(jù)并進(jìn)行解碼,。 3. 從來源獲取數(shù)據(jù)當(dāng) DecodeJob 從緩存中獲取不到數(shù)據(jù)時(shí),,就會(huì)把運(yùn)行理由改為 SWITCH_TO_SOURCE_SERVICE ,也就是從來源獲取數(shù)據(jù),,然后運(yùn)行來源數(shù)據(jù)生成器 SourceGenerator ,。 4. 對(duì)檢索到的數(shù)據(jù)進(jìn)行解碼DecodeJob 通過數(shù)據(jù)生成器獲取到數(shù)據(jù)后,就會(huì)調(diào)用 decodeFromRetrievedData() 方法來對(duì)檢索到的數(shù)據(jù)進(jìn)行解碼。 1.4.2 DecodeJob 數(shù)據(jù)獲取流程在 DecodeJob 的 getNextStage() 方法中,,會(huì)根據(jù)當(dāng)前的解碼步驟 stage 來判斷進(jìn)行什么操作,。 DecodeJob 把提取數(shù)據(jù)分為了 6 個(gè)階段,這 6 個(gè)階段是 Stage 枚舉類中的值,。 1. INITIALIZE初始化,。 當(dāng)解碼處于這個(gè)階段時(shí),DecodeJob 會(huì)根據(jù)磁盤緩存策略,,判斷是否要從磁盤緩存中獲取處理過的圖片資源,,是的話就用 ResourceCacheGenerator 獲取圖片資源,當(dāng)用 ResourceCacheGenerator 獲取到 Resource 后,,就會(huì)開始對(duì)資源進(jìn)行解碼,。 如果磁盤緩存策略設(shè)定了不從緩存中獲取 Resource,那就會(huì)切換到 RESOURCE_CACHE 階段,。 2. RESOURCE_CACHE從緩存中獲取處理過的圖片資源,。 當(dāng)解碼處于這個(gè)階段時(shí),DecodeJob 會(huì)根據(jù)磁盤緩存策略,,判斷是否要從磁盤緩存中獲取未處理過的圖片原始數(shù)據(jù),,是的話就用 DataCacheGenerator 獲取圖片數(shù)據(jù)。 3. DATA_CACHE從緩存中獲取原始數(shù)據(jù),。 如果磁盤緩存策略設(shè)定了不獲取緩存中的圖片資源和原始數(shù)據(jù) ,,又或者是獲取不到數(shù)據(jù),DecodeJob 那就會(huì)切換到 DATA_CACHE 階段,。 如果我們?cè)诩虞d圖片時(shí)調(diào)用了 onlyRetrieveFromCache(true) ,,那么 DecodeJob 就會(huì)不會(huì)切換到 SOURCE 階段從來源獲取數(shù)據(jù),而是會(huì)切換到 FINISH 階段結(jié)束數(shù)據(jù)獲取流程,。 否則就會(huì)切換到 SOURCE 階段。 4. SOURCE從圖片來源獲取原始數(shù)據(jù),。 如果 DecodeJob 在 RESOURCE_CACHE 和 DATA_CACHE 階段都沒有拿到圖片數(shù)據(jù),,那就會(huì)用 SourceGenerator 從圖片來源獲取圖片數(shù)據(jù)。 5. ENCODE編碼,。 當(dāng)磁盤緩存策略設(shè)定了要對(duì)圖片資源進(jìn)行緩存時(shí),,那么在獲取到數(shù)據(jù)后,DecodeJob 就會(huì)用 ResourceDecoder 對(duì)資源進(jìn)行編碼,,也就是把圖片放到磁盤緩存中,。 6. FINISH結(jié)束。 1.4.3 三種數(shù)據(jù)生成器當(dāng) DecodeJob 切換階段后,,會(huì)調(diào)用 getNextGenerator() 切換不同階段對(duì)應(yīng)的生成器,,這里說的生成器,指的是 DataFetcherGenerator 接口。 DataFetcherGenerator 不是像名字說的那樣用來創(chuàng)建 DataFetcher 的,,DataFetcherGenerator 與 DataFetcher 是通過 ModelLoader 來關(guān)聯(lián)的,。 DataFetcherGenerator 會(huì)通過 ModelLoader 構(gòu)建數(shù)據(jù)封裝對(duì)象 LoadData ,然后通過 LoadData 中的 DataFetcher 來加載數(shù)據(jù),。 LoadData 是 ModelLoader 的內(nèi)部類,,它有來源標(biāo)識(shí)符 Key 和 DataFetcher 兩個(gè)字段。 在 ModelLoader 中最重要的就是 buildLoadData() 方法,,不同類型的 Model 對(duì)應(yīng)的 ModelLoader 所創(chuàng)建出來的 LoadData() 也不同,。 下面我們來看下 DataFetcherGenerator ,這個(gè)接口中最重要的方法是 startNext() ,,具體實(shí)現(xiàn)了這個(gè)接口有下面三個(gè)類,。
以 SourceGenerator 為例,我們來看下 startNext() 方法的處理流程,。 1. 是否獲取到了需要緩存的數(shù)據(jù)當(dāng) SourceGenerator 加載完數(shù)據(jù)后,,會(huì)再次進(jìn)入 startNext() 方法,這時(shí)就獲取到了需要緩存的數(shù)據(jù),。 2. 是否保存原始數(shù)據(jù)如果磁盤緩存策略設(shè)定了要保存圖片的原始數(shù)據(jù),,就用數(shù)據(jù)提取器加載數(shù)據(jù),否則就直接把圖片加載給 Target ,。 3. 加載數(shù)據(jù)當(dāng)需要保存原始數(shù)據(jù)或數(shù)據(jù)有加載路徑時(shí),,SourceGenerator 就會(huì)根據(jù) Model 的類型,使用對(duì)應(yīng)的 DataFetcher 來提取數(shù)據(jù),,比如從網(wǎng)絡(luò)上下載圖片,。 4. 是否保存原始數(shù)據(jù)當(dāng) SourceGenerator 獲取到數(shù)據(jù)后,會(huì)再次判斷是否要保存原始數(shù)據(jù),,否則就直接把圖片加載給 Target ,。 5. 編碼當(dāng) SourceGenerator 從 DataFetcher 中拿到數(shù)據(jù)后,會(huì)再走一遍 startNext() 方法,,然后用編碼器 Encoder 對(duì)數(shù)據(jù)進(jìn)行編碼,,也就是把圖片放到磁盤緩存中。 6. 從磁盤中獲取數(shù)據(jù)當(dāng) SourceGenerator 把數(shù)據(jù)保存到磁盤后,,不會(huì)直接加載圖片,,而是從磁盤中拿這張圖片,然后再進(jìn)行加載,。 1.4.4 onResourceDecoded()當(dāng) DecodeJob 調(diào)用 ResourceDecoder 的 decode() 方法,,并且獲取到編碼結(jié)果后,,會(huì)調(diào)用 onResourceDecoded() 方法應(yīng)用變換選項(xiàng)以及初始化編碼管理器。 1. 應(yīng)用變換選項(xiàng)對(duì)于處理過的 Resource,,onResourceDecoded() 不會(huì)再次進(jìn)行變換,,否則就會(huì)對(duì)圖片進(jìn)行變換操作。 2. 回收?qǐng)D片資源當(dāng)對(duì)資源應(yīng)用了變換選項(xiàng)后,,DecodeJob 會(huì)把原來的資源回收掉,,因?yàn)檫@個(gè)資源接下來也用不上了。 3. 緩存變換后圖片資源onResourceDecoded() 方法中,,會(huì)根據(jù)磁盤緩存策略判斷是否要對(duì)資源進(jìn)行編碼,,如果要進(jìn)行編碼的話,會(huì)根據(jù)不同的編碼策略創(chuàng)建不同的 Key ,。 Glide 有 SOURCE 和 TRANSFORMED 兩種編碼策略,,分別代表對(duì)原始數(shù)據(jù)進(jìn)行編碼和對(duì)變換后資源進(jìn)行編碼。
4. 初始化編碼管理器創(chuàng)建好 Key 后不會(huì)直接對(duì)圖片進(jìn)行編碼,而是會(huì)修改編碼管理器的 Key ,,等到轉(zhuǎn)碼完成后再用 ResourceEncoder 進(jìn)行編碼,。 1.4.5 DecodeJob 相關(guān)問題
2. 六步加載圖片看完了解碼任務(wù)啟動(dòng)流程,,下面我們來看下當(dāng) DecodeJob 獲取到圖片數(shù)據(jù)后是怎么處理這些數(shù)據(jù)的,,在文章的開頭已經(jīng)講過 Glide 解碼大致的 5 步,這里再補(bǔ)充一個(gè),,就是在把圖片加載到 Target 后,,DecodeJob 會(huì)通過 ResourceEncoder 把圖片保存到本地。 其中關(guān)于 Target 在 1.2 小節(jié)已經(jīng)講過,,下面就不再多講了,,我們來看下其他的對(duì)象。 Glide 對(duì)圖片解碼的過程涉及下面 6 個(gè)概念,。
1. 數(shù)據(jù)來源(Model)Glide 會(huì)以 Model 的形式封裝圖片來源 ,,Model 可以是 URL,、本地文件和網(wǎng)絡(luò)圖片等類型,。 2. 原始數(shù)據(jù)(Data)Glide 把數(shù)據(jù)源轉(zhuǎn)換為Model 后,會(huì)把它加工成原始數(shù)據(jù) Data ,,一般就是輸入流 InputStream ,,Glide 會(huì)把這些輸入流封裝為 Data ,,而 ModelLoader 則負(fù)責(zé)從 Data 獲取原始數(shù)據(jù)。 3. 資源(Resource)獲取到原始數(shù)據(jù)后,,Glide 會(huì)用資源解碼器 ResourceDecoder 對(duì)原始數(shù)據(jù)進(jìn)行解碼,,比如把輸入流 InputStream 解碼為 Bitmap,解碼后的資源就是 Resource ,。 4. 變換后資源(TransformedResource)Glide 會(huì)根據(jù)我們的變換選項(xiàng)處理 Resource ,,比如用 centerCrop() 裁剪就是一種變換,變換后的 Resource 就叫 TransformedResource ,,負(fù)責(zé)轉(zhuǎn)換的就是 Transformation ,。 5. 轉(zhuǎn)碼后資源(TranscodedResource)Glide 除了能加載靜態(tài)圖片,還能加載 Gif 動(dòng)態(tài)圖,,解碼后的 Bitmap 和 Gif 的類型不是統(tǒng)一的,,為了統(tǒng)一處理靜態(tài)和動(dòng)態(tài)圖片,Glide 會(huì)把 Bitmap 轉(zhuǎn)換為 GlideBitmapDrawable ,,而負(fù)責(zé)轉(zhuǎn)碼的角色則是 ResourceTranscoder ,。 6. 目標(biāo)(Target)Glide 最終會(huì)把圖片顯示到目標(biāo) Target 上,比如 ImageView 對(duì)應(yīng)的就是 ImageViewTarget ,。 2.1 ModelLoaderModelLoader 是一個(gè)接口,,負(fù)責(zé)創(chuàng)建 LoadData ,它有兩個(gè)泛型參數(shù) Model 和 Data,。
1. Factory在 DataFetcherGenerator 獲取圖片數(shù)據(jù)時(shí),會(huì)調(diào)用 ModelLoaderRegistry 的 getModelLoaders() 方法,,這個(gè)方法中會(huì)根據(jù) model 的類型用 MultiModelLoaderFactory 生成對(duì)應(yīng)的 ModelLoader,,比如能夠解析字符串的 ModelLoader 就有 7 個(gè),關(guān)于 ModelLoaderRegistry 在后面講 Glide 配置的時(shí)候會(huì)講到,。 此外每一個(gè) ModelLoader 的實(shí)現(xiàn)類中都定義了一個(gè)實(shí)現(xiàn)了 ModelLoaderFactory 接口的靜態(tài)內(nèi)部類 ,。 2. handles()一個(gè) Model 對(duì)應(yīng)這么多 ModelLoader,每個(gè) ModelLoader 加載數(shù)據(jù)的方式都不同,,這時(shí)候就要用 handles() 方法了,。 ModelLoader 接口有 handles() 和 buildLoadData() 兩個(gè)方法,handles() 用于判斷某個(gè) Model 是否能被自己處理,,比如 HttpUriLoader 的 handles() 會(huì)判斷傳進(jìn)來的字符串是否以 http 或 https 開頭,,是的話則可以處理。 3. buildLoadData()ModelLoader 之間是存在嵌套關(guān)系的,,比如 HttpUriLoader 的 buildLoadData() 方法就是調(diào)用的 HttpGlideUrlLoader 的 buildLoadData() 方法,,HttpGlideUrlLoader 會(huì)創(chuàng)建一個(gè) HttpUrlFetcher ,,然后把它放到 LoadData() 中。 LoadData 是 ModelLoader 中定義的一個(gè)類,,它只是放置了圖片來源的 Key 和要用來提取數(shù)據(jù)的 DataFetcher ,,沒有其他方法。 2.2 ResourceDecoderDataFetcherGenerator 使用 ModelLoader 構(gòu)建完數(shù)據(jù)后,,就會(huì)用 DataRewinder 對(duì)數(shù)據(jù)進(jìn)行重繞,,也就是重置數(shù)據(jù),比如 InputStreamRewinder 就會(huì)調(diào)用 RecyclableBufferedInputStream 的 reset() 方法重置輸入流對(duì)應(yīng)的字節(jié)數(shù)組的位置,。 ResourceDecoder 是一個(gè)接口,,有非常多的實(shí)現(xiàn)類,比如網(wǎng)絡(luò)圖片對(duì)應(yīng)的解碼器為 StreamBitmapDecoder ,,StreamBitmapDecoder 的 decode() 方法調(diào)用了降采樣器 Downsampler 的 decode() 方法,,下圖是 Downsampler 的解碼邏輯。 1. 設(shè)置目標(biāo)寬高除非我們通過 override() 方法把尺寸改為 Target.SIZE_ROGINAL ,,否則 Glide 默認(rèn)會(huì)把 ImageView 的大小作為加載圖片的目標(biāo)寬高,。 2. 計(jì)算縮放后寬高根據(jù)不同的變換選項(xiàng)計(jì)算縮放后寬高。 3. 創(chuàng)建空 Bitmap根據(jù)計(jì)算后的目標(biāo)寬高創(chuàng)建一個(gè)空的 Bitmap ,。 4. 使用 BitmapFactory 解碼Downsampler 的解碼方式用的是 ImageReader 的 decodeBitmap() 方法,,而 ImageReader 又調(diào)用了 BitmapFactory 的 decodeStream() 方法,BitmapFactory 最終調(diào)用的是 SkImageDecoder 的 decode() 方法,。 5. 把 Bitmap 放入 BitmapPool 中在前面講 Resource 的時(shí)候講到了 BitmapResource 中有一個(gè) BitmapPool,,這個(gè) BitmapPool 是由 Downsampler 傳過去的,而 Downsampler 的 BitmapPool 是由 Glide 創(chuàng)建并傳進(jìn)來的,。 2.3 TransformationTransformation 是一個(gè)接口,,它有一個(gè) transform() 方法,這個(gè)方法是在 DecodeJob 中調(diào)用的,,當(dāng) DecodeJob 發(fā)現(xiàn)數(shù)據(jù)源不是緩存中的 Resource 時(shí),,就會(huì)調(diào)用變換選項(xiàng)的 transform() 方法。 Transformation 的其中一個(gè)實(shí)現(xiàn)類是 BitmapTransformation,,我們平時(shí)調(diào)用的 centerCrop() 就是 BitmapTransformation 的子類,,centerCrop() 選項(xiàng)對(duì)應(yīng)的是 CenterCrop 類,它實(shí)現(xiàn)了 Transformation 接口,,具體的變換實(shí)現(xiàn)在 TransformationUtils 中,。 1. Matrix以 centerCrop() 為例,TransformationUtils 的 centerCrop() 方法會(huì)先創(chuàng)建一個(gè) Matrix 矩陣,,然后根據(jù)傳進(jìn)來的 Bitmap 計(jì)算 Matrix 的縮放比例和平移坐標(biāo),。 2. drawBitmap()配置好 Matrix 后,就會(huì)根據(jù)目標(biāo)寬高創(chuàng)建一個(gè)空的目標(biāo) Bitmap ,,然后把原始 Bitmap,、目標(biāo) Bitmap 和 Matrix 傳給 Canvas 的 drawBitmap() 方法,然后返回 Canvas 處理好的圖片,。 2.4 ResouceTranscoderResourceTranscoder 是一個(gè)接口,,是 Glide 中的資源轉(zhuǎn)碼器,它有兩個(gè)泛型參數(shù) Z 和 R ,,分別代表需要進(jìn)行原始類型和轉(zhuǎn)碼目標(biāo)類型,。 比如 BitmapDrawableTranscoder 的原始類型是 Bitmap,轉(zhuǎn)碼目標(biāo)類型是 BitmapDrawable,,在BitmapDrawableTranscoder 的 transcode() 方法中,,會(huì)把 Bitmap 轉(zhuǎn)換為 BitmapDrawable ,以便 Target 進(jìn)行處理,。 2.5 ResourceEncoderResourceEncoder 是一個(gè)接口,,是 Glide 中的資源編碼器,ResourceEncoder 有好幾個(gè)實(shí)現(xiàn)類,,比如網(wǎng)絡(luò)圖片對(duì)應(yīng)的編碼器為 StreamEncoder,。 在轉(zhuǎn)碼完成后,DecodeJob 會(huì)先把圖片加載到 Target 中,,然后用 ResourceEncoder 對(duì)圖片進(jìn)行編碼,,比如 StreamEncoder 的編碼操作就是把輸入流 InputStream 轉(zhuǎn)化為圖片文件,然后保存到本地,。 2.6 圖片加載相關(guān)問題
3. Glide 緩存原理Glide 使用了三級(jí)緩存機(jī)制,,圖片的緩存分為內(nèi)存,、磁盤和來源,也就是從內(nèi)存獲取不到圖片時(shí),,再去磁盤獲取圖片,,從磁盤獲取不到圖片時(shí),再從圖片來源獲取圖片,。 三級(jí)緩存的優(yōu)勢(shì)在于節(jié)省流量和內(nèi)存,,如果不用三級(jí)緩存,每次都從服務(wù)端獲取圖片的話,,圖片消耗的流量就會(huì)非常多,,如果把所有圖片都放在內(nèi)存的話,,那就有可能發(fā)生 OOM 。 下面我們來看下 Glide 的內(nèi)存緩存原理,、磁盤緩存原理和磁盤緩存策略,。 3.1 Glide 內(nèi)存緩存原理前面提到 Engine 的 load() 方法會(huì)先在內(nèi)存緩存中查找 Key 對(duì)應(yīng)的資源,沒有的話再啟動(dòng)新的解碼任務(wù),。 這里說的內(nèi)存緩存就是 MemoryCache,,MemoryCache 是一個(gè)接口,它的實(shí)現(xiàn)類是 LruResourceCache,。 LruResourceCache 不僅實(shí)現(xiàn)了 MemoryCache 接口,,而且還是 LruCache 的子類,具體的內(nèi)存緩存實(shí)現(xiàn)是在 LruCache 中,。 在 LruCache 的 put() 方法中,,首先會(huì)判斷要保存的元素大小是否大于緩存最大值,如果是的話,,則不進(jìn)行保存,,如果不是的話,則把當(dāng)前容量加上元素的大小,,并把該元素放入緩存,。 LruCache 比較特別的就是它的 trimToSize() 方法和 LinkedHashMap 的 accessOrder 屬性。 1. trimToSize()LruCache 在用 put() 方法保存新的元素時(shí),,它會(huì)通過 trimToSize() 方法移除最近最少使用的元素,。 2. accessOrderLruCache 中是用 LinkedHashMap 保存數(shù)據(jù)的,并且這個(gè) LinkedHashMap 的 accessOrder 的值為 true,,也就是每一次獲取 LinkedHashMap 中的元素時(shí),,這個(gè)元素都會(huì)被移到鏈表的尾端。 3.2 Glide 磁盤緩存原理Glide 是用 DiskCache 保存圖片文件的,,DiskCache 是一個(gè)接口,,這個(gè)接口中還定義了 Factory 和 Writer 兩個(gè)接口,Writer 只是對(duì) ResourceEncoder 的封裝,。 下面我們就來看看 DiskCache 和 DiskCache.Factory 的具體實(shí)現(xiàn),。 3.2.1 DiskLruCacheDiskCache 有兩個(gè)實(shí)現(xiàn)類, DiskCacheAdapter 和 DiskLruCacheWrapper,,DiskCacheAdapter 只是一個(gè)空實(shí)現(xiàn),。 從名字可以看得出來 DiskLruCacheWrapper 是對(duì) DiskLruCache 的封裝,具體的實(shí)現(xiàn)是在 DiskLruCache 中,,DataCacheGenerator 和 ResourceCacheGenerator 都是用的 DiskLruCache 來獲取磁盤緩存數(shù)據(jù)的,。 1. Entry和 LruCache 一樣,DiskLruCache 中也有一個(gè) LinkedHashMap ,這個(gè) HashMap 的 Key 的類型為 String,,Value 的類型為 Entry,,從緩存中獲取到的圖片文件會(huì)放在 Entry的 cleanFiles 字段中。 2. Editor當(dāng)圖片加載進(jìn)入編碼階段時(shí),,DecodeJob 會(huì)通過編碼管理器調(diào)用 DiskLruCacheWrapper 的 put() 方法保存圖片文件,。 在 DiskLruCacheWrapper 的 put() 方法中,會(huì)通過 DiskCache 的緩存編輯器 Editor 獲取圖片文件,,獲取到圖片文件后,就會(huì)用 Writer 把文件寫入本地,,寫完后再調(diào)用 Editor 的 commit() 方法,,把清理緩存的回調(diào)提交到清理線程池中。 3. 清理資源DiskLruCache 中有一個(gè)執(zhí)行清理資源任務(wù)的線程池,,線程池的線程數(shù)最多為 1,, 這個(gè)線程池要執(zhí)行的任務(wù)為 cleanupCallback 回調(diào),這個(gè)回調(diào)會(huì)執(zhí)行 trimToSize() 方法,,為的就是把最近最少使用的文件清除掉,。 3.2.2 DiskCache.Factory在 DiskCache 中有一個(gè) Factory 工廠接口,這個(gè)接口用在了 Engine 的 LazyDiskCacheProvider 中,。 在 Factory 接口中,,定義了默認(rèn)的磁盤緩存大小為 250M,默認(rèn)的緩存目錄名稱為 "image_manager_disk_cache" ,。 Factory 主要有下面 2 個(gè)實(shí)現(xiàn)類,。
默認(rèn)情況下 Glide 用的是 InternalCacheDiskCacheFactory ,,如果想把圖片放在外部緩存目錄的話,,可以在自定義的 GlideModule 設(shè)置 DiskCache 。 3.3 Glide 磁盤緩存策略在加載圖片時(shí),,我們可以用 diskCacheStratgy() 方法設(shè)置圖片在磁盤的緩存策略,,這個(gè)選項(xiàng)傳入的參數(shù)類型為抽象類 DiskCacheStrategy。 磁盤緩存策略涉及到 Glide 的數(shù)據(jù)源類型 DataSource 和編碼策略 EncodeStratefy,,編碼策略前面講過了,,下面我們先來看看數(shù)據(jù)源 DataSource。 3.3.1 五種數(shù)據(jù)源Glide 中定義了下面 5 種數(shù)據(jù)源 DataSource,。
3.3.2 四個(gè)抽象方法DiskCacheStrategy 有下面 4 個(gè)抽象方法,,這個(gè) 4 個(gè)方法的返回值都是布爾值,。
1. isDataCacheable()是否保存圖片的原始數(shù)據(jù)。 DecodeJob 中用到的 SourceGenerator 在從圖片來源獲取到數(shù)據(jù)后,,會(huì)根據(jù)這個(gè)方法判斷是否保存圖片的原始數(shù)據(jù),。 2. isResourceCacheable()是否保存解碼后的圖片數(shù)據(jù)。 當(dāng)資源解碼器對(duì)圖片數(shù)據(jù)進(jìn)行解碼后,,DecodeJob 就會(huì)根據(jù)這個(gè)方法的返回值決定是否保存該 Resource ,。 3. decodeCachedResource()是否對(duì)緩存的解碼后的圖片數(shù)據(jù)進(jìn)行解碼。 在 DecodeJob 的 getNextStage() 中,,會(huì)根據(jù)這個(gè)方法的返回值判斷,,如果返回值為 false,意味著跳過 RESOURCE_CACHE 步驟,,也就是不對(duì)緩存中處理過的圖片資源進(jìn)行處理,。 4. decodeCachedData()是否對(duì)緩存的原始數(shù)據(jù)進(jìn)行解碼。 在 DecodeJob 的 getNextStage() 方法中,,會(huì)根據(jù)這個(gè)方法的返回值判斷,,如果該值為 false,意味著跳過 DATA_CACHE 步驟,,也就是不對(duì)緩存中的原始圖片數(shù)據(jù)進(jìn)行處理,。 4.3.2 五種緩存策略Glide 定義好的磁盤緩存策略有下面 5 種,默認(rèn)為 AUTOMATIC,。
1. AUTOMATIC
2. ALL
3. DATA
4. RESOURCE
5. NONE所有方法的返回值都為 false。 3.4 BitmapPool1. 減少 Bitmap 占用的內(nèi)存BitmapResource 的 BitmapPool 用的就是 GlideBuilder 中的 BitmapPool,,Downsampler 在解碼后,,會(huì)把圖片放入 BitmapPool 中,當(dāng) BitmapResource 被回收時(shí),,也會(huì)把 Bitmap 放到 BitmapPool 中,。 具體需要用到 BitmapPool 中的 Bitmap 的地方在 TransformationUtils 中,TransformationUtils 在進(jìn)行變換前會(huì)從 BitmapPool 中獲取之前保存的 Bitmap,。 之所以要這么做,,是因?yàn)槊恳淮巫儞Q都需要?jiǎng)?chuàng)建一個(gè) Bitmap ,BitmapPool 就是為了復(fù)用這個(gè) Bitmap 占用的內(nèi)存,,這樣下次要做變換操作時(shí),,可以用同一個(gè) Bitmap 就進(jìn)行復(fù)用,,以減少內(nèi)存使用,。 比如對(duì)于 RecyclerView 中的圖片,它們的大小是一樣的,,沒必要在變換時(shí)為每張圖片都創(chuàng)建一個(gè)新的 Bitmap,。 2. LruPoolStrategyBitmapPool 是一個(gè)接口,實(shí)現(xiàn)類為 LruBitmapPool ,,具體的邏輯在 LruPoolStrategy 中,。 LruPoolStrategy 也是一個(gè)接口,它的實(shí)現(xiàn)類為 SizeConfigStrategy,,從 LruPoolStrategy 的名字可以看得出來,,BitmapPool 用的是 LruCache 來保存 Bitmap 的。 在 LruPoolStrategy 中,,會(huì)根據(jù) Bitmap 的大小和編碼選項(xiàng),,把 Bitmap 放到 GroupedLinkedHamp 中。 3.5 ArrayPool和 BitmapPool 一樣,,ArrayPool 用的也是 LruCache,,也是為了減少不必要的內(nèi)存浪費(fèi)。 比如在輸入流編碼器 StreamEncoder 中,,當(dāng)把輸入流轉(zhuǎn)化為文件時(shí),,需要?jiǎng)?chuàng)建一個(gè)新的字節(jié)數(shù)組,如果不用 ArrayPool,,而圖片是在列表中加載的,,那就會(huì)創(chuàng)建很多不必要的的字節(jié)數(shù)組。 3.6 Glide 緩存相關(guān)問題
4. Glide 初始化流程與配置4.1 Glide 初始化流程在看 Glide 的配置前,我們先來看下 Glide 的初始化流程,,因?yàn)樽x取配置就是在初始化的過程中讀取的,。 4.1.1 with()當(dāng)我們調(diào)用 Glide.with() 方法時(shí),Glide 會(huì)先用 getRetriever() 方法獲取請(qǐng)求管理器檢索器,,在這個(gè)方法中還會(huì)用 get() 方法獲取 Glide 實(shí)例,,獲取不到的話就會(huì)初始化 Glide 。 4.1.2 initializeGlide()我們可以在 AndroidManifest 中聲明 GlideModule,,也可以用 @GlideModule 注解聲明 GlideModule,,走的都是上面這個(gè)流程。 1. 應(yīng)用選項(xiàng)Glide 有一個(gè) ApppModuleGenerator,,它會(huì)把讀取我們?cè)O(shè)定的 AppGlideModule 中的配置,,然后生成一個(gè) GeneratedAppGlideModuleImpl 配置。 然后用反射讀取這個(gè)配置,,讀取到配置后,,就會(huì)應(yīng)用我們?cè)?applyOptions() 中給 GlideBuilder 設(shè)置的選項(xiàng)。 如果不用生成加反射的話讀取配置的話,,Glide 并不知道我們會(huì)把配置叫什么,,放哪里。 2. 創(chuàng)建實(shí)例應(yīng)用選項(xiàng)后,,就會(huì)創(chuàng)建一個(gè) Glide 實(shí)例,。 3. 注冊(cè)組件創(chuàng)建完實(shí)例后,就會(huì)把實(shí)例的 registry 傳到 registerComponents() 中,,也就是我們修改編解碼邏輯的地方,。 4. 注冊(cè)回調(diào)Glide 實(shí)現(xiàn)了 ComponentCallback 用于監(jiān)聽內(nèi)存狀態(tài),這里的注冊(cè)回調(diào)就是調(diào)用 ApplicationContext 的 registerComponentCallbacks() 方法,。 4.2 RegistryGlide 有一個(gè)登記處 Registry ,,它包含了下面這些 Registry 。
在 Glide 的構(gòu)造方法中,,會(huì)把所有的編碼,、解碼和數(shù)據(jù)加載等邏輯通過 Registry 的 append() 方法登記到 Registry 中,我們可以在 AppGlideModule 的 registerComponents() 方法中獲取到 registry 實(shí)例,,通過這個(gè)實(shí)例就可以替換掉對(duì)應(yīng)的實(shí)現(xiàn),。 1. registerComponents()比如獲取網(wǎng)絡(luò)圖片默認(rèn)用的是 HttpUrlFetcher ,HttpUrlFetcher 是用的 HttpURLConnection 來獲取圖片數(shù)據(jù)的,,我們可以在 registerComponents() 方法中,,把 HttpUrlFetcher 替換為 OkHttp ,。 2. Entry之所以要用 Class 來替換 ModelLoader ,是因?yàn)?ModelLoaderRegistry 的 append() 方法會(huì)用來源類型(Model),、原始數(shù)據(jù)類型(Data)和 ModelLoaderFactory 來創(chuàng)建不同類型的 Entry ,,這些 Entry 會(huì)保存在 MultiModelLoaderFactory 工廠中。 當(dāng) DataFetcherGenerator 通過 ModelLoader 獲取數(shù)據(jù)時(shí),,則會(huì)通過 model 的 Class 信息來獲取 ModelLoader,,除了 ModelLoaderRegistry,其他的 Registry 中也有 Entry,。 4.3 GlideBuilderGlideBuilder 就是 Glide 的構(gòu)建器,,它包含了下面這些數(shù)據(jù)。
上面這些字段大多數(shù)都是可以在 AppGlideModule 的 applyOptions() 方法中,,調(diào)用 GlideBuilder 的 setXXX() 方法來替換實(shí)現(xiàn)的,,下面我們主要看下 Glide 線程池和內(nèi)存大小計(jì)算器。 4.3.1 Glide 線程池GlideExecutor 是 Glide 的線程池實(shí)現(xiàn),,Glide 中有下面 4 種線程池,。 1. SourceExecutor對(duì)圖片來源解碼的任務(wù)的線程池,線程數(shù)為最多為 4 ,,最小為設(shè)備的 CPU 核數(shù),。 2. unlimitedSourceExecutor如果我們?cè)诩虞d圖片時(shí)調(diào)用了 useUnlimitedSourceGeneratorsPool() 選項(xiàng),那 Glide 就會(huì)用這個(gè)無線程數(shù)限制的線程池來獲取圖片,。 3. DiskCacheExecutor對(duì)磁盤緩存數(shù)據(jù)解碼的任務(wù)的線程池,,線程數(shù)為 1 ,。 4. AnimationExecutor這也是用來從圖片來源獲取數(shù)據(jù)的線程池,,而不是用來播放 GIF 動(dòng)畫的線程池,當(dāng)我們加載圖片時(shí)調(diào)用了 useAnimationPool(true) ,,那在獲取圖片數(shù)據(jù)時(shí) EngineJob 就會(huì)把 DecodeJob 放到 AnimationExecutor 中,。 如果設(shè)備 CPU 核數(shù)大于等于 4 ,那 AnimationPool 線程數(shù)就是 2 ,,否則就是 1 ,。 5. 自定義線程池Glide 對(duì)于線程池只允許用 GlideBuilder 中的 Builder 來設(shè)置參數(shù),GlideExecutor.Builder 支持下面幾個(gè)參數(shù),。
4.3.2 內(nèi)存大小計(jì)算器MemorySizeCalculator 負(fù)責(zé)計(jì)算 BitmapPool ,、ArrayPool 和 MemoryCache 的大小。 1. BitmapPool 大小BitmapPool 大小為一屏可容納的最高圖片質(zhì)量的大小,,比如 1080 * 1920 * 4 ≈ 7.9M ,。 2. ArrayPool 大小默認(rèn)為 4M,如果系統(tǒng)版本低于 19 則為 2M,。 3. 內(nèi)存緩存大小內(nèi)存緩存大小為兩屏可容納的最高圖片質(zhì)量的大小,,比如 1080 * 1920 * 2 * 4 ≈ 15.8M 。 4. maxSizeMultiplierMemorySizeCalculator 在計(jì)算 BitmapPool 和 MemoryCache 大小時(shí),,會(huì)通過 getMaxSize() 方法,,用 ActivityManager 獲取 memoryClasss,然后用 memoryClass 的值乘以 maxSizeMultiplier,,maxSizeMultiplier 默認(rèn)為 0.4,。 memoryClass 就是獲取應(yīng)用可用內(nèi)存大小,比如我的 VIVO 手機(jī)給應(yīng)用分配的可用內(nèi)存為 256M,,以我的手機(jī)為例,,256 * 0.4 = 102.4 ,也就是默認(rèn)情況下,, BitmapPool ,、ArrayPool 和 MemoryCache 的大小最多不會(huì)超過 102.4M。 當(dāng) BitmapPool ,、ArrayPool 和 MemoryCache 的大小加起來大于最大值時(shí),,會(huì)按這個(gè)最大值重新計(jì)算 BitmapPool 和 MemoryCache 的大小。 5. 自定義內(nèi)存大小計(jì)算方式和 GlideExecutor 一樣,,MemorySizeCalculator 也有一個(gè) Builder,,支持下面這些參數(shù)的設(shè)置。
4.4 初始化流程與配置相關(guān)問題
5. Glide 圖片加載選項(xiàng)1. placeholder(drawable)如果用戶打開 App 的時(shí)候,,本來應(yīng)該顯示圖片的控件,,由于網(wǎng)絡(luò)原因等了好幾秒都沒加載出來,這樣用戶體驗(yàn)就不好,,所以我們可以加上一張占位圖,,這樣用戶就知道圖片等下就出來了。 這里要注意的是,,占位圖只能是 App 內(nèi)置圖片,,不能是網(wǎng)絡(luò)圖片,否則無網(wǎng)絡(luò)的時(shí)候它就沒作用了,。 2. error(drawable)當(dāng)用戶的網(wǎng)絡(luò)出現(xiàn)錯(cuò)誤,,圖片加載失敗時(shí),一直顯示占位圖,,用戶就會(huì)一直等待,,如果等了半天都沒加載出來,用戶就會(huì)覺得我們的 App 有問題,。 這時(shí)候我們可以用一張錯(cuò)誤占位圖,,這樣用戶就知道有可能是網(wǎng)絡(luò)出問題了,切換一下網(wǎng)絡(luò),,又或者是主動(dòng)聯(lián)系開發(fā)者,。 如果沒有設(shè)置這個(gè)參數(shù)的話,出錯(cuò)時(shí)會(huì)顯示 placeholder 中傳的占位圖,。 3. fallback()設(shè)置當(dāng)數(shù)據(jù)模型為空,,也就是我們傳入 load() 中的值為空時(shí)要顯示的圖片,沒有設(shè)置 fallback 會(huì)顯示錯(cuò)誤占位圖,,連錯(cuò)誤占位圖也沒設(shè)置就會(huì)顯示 placeholder 占位圖,。 4. override(width, height)如果我們不想讓 Glide 把圖片按 ImageView 的大小進(jìn)行縮放,,我們可以用這個(gè)方法來設(shè)置加載的目標(biāo)寬高,。 5. fitCenter()fitCenter 是一個(gè)圖片裁剪選項(xiàng),用于把圖片尺寸限定在 ImageView 內(nèi)并居中,,這樣圖片就能完全顯示,。 選擇這個(gè)選項(xiàng)后,當(dāng)圖片的寬高比和 ImageView 的寬高比不同時(shí),,ImageView 就不會(huì)被填滿,。 6. centerCrop()當(dāng)我們使用了 centerCrop (),并且圖片寬高比與 ImageView 不同時(shí),,Glide 會(huì)裁剪中間的部分,,以填滿 ImageView ,,這時(shí)圖像就不是完全顯示的了。 7. centerInside()與 fitCenter 類似,,不同的是,,當(dāng) ImageView 的尺寸為 wrap_content 時(shí),fitCenter() 會(huì)把圖片放大,,而 centerInside() 則會(huì)保持原圖大小,。 8. transform(Transformation)設(shè)置變換選項(xiàng),比如旋轉(zhuǎn)選項(xiàng) Rotate 和圓角選項(xiàng) RoundedCorners 沒有對(duì)應(yīng)的方法可以直接設(shè)置,,就可以用這個(gè)方法傳進(jìn)去,。 如果想要同時(shí)多種變換選項(xiàng),也要從這個(gè)參數(shù)傳進(jìn)去,,比如 transform(CenterCrop(), RoundedCorners()) ,,這樣創(chuàng)建的就是多重變換 MultiTransformation ,否則只有后面設(shè)置的變換選項(xiàng)會(huì)起效,。 9. dontTransform()禁止變換,,調(diào)用這個(gè)方法后會(huì)刪除之前設(shè)定的變換選項(xiàng)。 10. skipMemoryCache(boolean)跳過內(nèi)存緩存,。 默認(rèn)情況下 Glide 會(huì)把圖片放在內(nèi)存緩存中,,如果我們不想要讓某張圖片保留在內(nèi)存緩存中,比如加載高清原圖時(shí),,可以把這個(gè)值改為 true ,。 11. diskCacheStrategy(strategy)磁盤緩存策略,Glide 自帶了 5 種磁盤緩存策略,,默認(rèn)為 AUTOMATIC,。
12. onlyRetrieveFromCache(boolean)如果傳 true ,表示不從圖片來源獲取數(shù)據(jù),,只從緩存中讀取數(shù)據(jù),。 13.priority設(shè)置加載圖片的優(yōu)先級(jí),比如運(yùn)營圖的優(yōu)先級(jí)就比其他圖片高,,Glide 的加載優(yōu)先級(jí)有下面四種,。
14. sizeMultiplier(float)按比例縮放圖片,。 15. encodeFormat(CompressFormat)設(shè)置下載或緩存的圖片的編碼類型,,比如 JPEG、PNG,、WEBP,。 如果沒有設(shè)置 encodeFormat,,并且圖像有透明通道,那 Glide 默認(rèn)會(huì)把以 PNG 的方式保存圖片,,否則就是 JPEG,。 16. encodeQuality(int)設(shè)置下載或緩存的圖片的編碼質(zhì)量,這個(gè)參數(shù)會(huì)傳到 Bitmap.compress() 方法中,。 17. frame(long)取視頻中某一幀的作為圖片,。 18. format(DecodeFormat)設(shè)置解碼格式,比如 ARGB_8888,、RGB_565 ,。 19. timeout(milliseconds)設(shè)置網(wǎng)絡(luò)圖片的請(qǐng)求超時(shí),默認(rèn)為 2500 毫秒,。 20. transition(options)設(shè)置過渡動(dòng)畫,,這里的 options 傳的是 TransitionOptions ,這是一個(gè)抽象類,,它有三個(gè)子類,。
21. dontAnimate()不播放 GIF 動(dòng)畫 ,。 22. apply(options)應(yīng)用選項(xiàng),。 23. listener(RequestListener)設(shè)置請(qǐng)求監(jiān)聽器 ,這個(gè)接口有下面兩個(gè)回調(diào),。
24. asBitmap()把解碼類型改為 Bitmap ,。 這個(gè)方法并不是 BaseRequestOptions 提供的,,而是 RequestManager 中的,但是對(duì)于外部使用者來說,,這就是一個(gè)加載選項(xiàng),,不同的是,這個(gè)選項(xiàng)需要在 load() 方法前調(diào)用,。 25.asFile()把解碼類型改為 File,,也就是用 submit().get() 獲取到的類型為 File 。 26. submit()我們可以用 submit() 方法來獲取我們想要的圖片類型,,比如文件、Bitmap 和 Drawable 等,,但是要注意的是這個(gè)方法要在子線程中執(zhí)行,。 27. downloadOnly()設(shè)定只下載圖片,,不加載圖片。 這個(gè)選項(xiàng)就是 asFile() + diskCacheStrategy(DATA) + priority(LOW) + skiptMmoeryCache(true) ,。 28. download()這個(gè)方法相當(dāng)于是用 download(image) 替換 downloadOnly().load(image) ,。 29. preload(width, height)預(yù)加載圖片,可以用來提前下載一些下一次啟動(dòng)應(yīng)用的時(shí)候會(huì)用到的圖片,,比如閃屏頁廣告,。 和 downloadOnly() 的區(qū)別在于不需要在子線程調(diào)用。 參考資料
|
|