基本使用流程
Glide最基本的使用流程就是下面這行代碼,其它所有擴展的額外功能都是以其建造者鏈式調用的基礎上增加的。
GlideApp.with(context).load(url).into(iv);
其中的GlideApp是註解處理器自動生成的,要使用GlideApp,必須先配置應用的AppGlideModule模塊,裏面可以爲空配置,也可以根據實際情況添加指定配置。
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// 實際使用中根據情況可以添加如下配置
<!--builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));-->
<!--int memoryCacheSizeBytes = 1024 * 1024 * 20;-->
<!--builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));-->
<!--int bitmapPoolSizeBytes = 1024 * 1024 * 30;-->
<!--builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));-->
<!--int diskCacheSizeBytes = 1024 * 1024 * 100;-->
<!--builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes));-->
}
}
源碼解析
Glide框架圖
GlideApp.with(context)
- 初始化各式各樣的配置信息(包括緩存,請求線程池,大小,圖片格式等等)以及glide對象。
- 將glide請求和application/SupportFragment/Fragment的生命週期綁定在一塊。
with(context)加載流程圖
load(url)
設置請求url,並記錄url已設置的狀態。
into(iv)
-
首先根據轉碼類transcodeClass類型返回不同的ImageViewTarget:BitmapImageViewTarget、DrawableImageViewTarget。
-
遞歸建立縮略圖請求,沒有縮略圖請求,則直接進行正常請求。
-
如果沒指定寬高,會根據ImageView的寬高計算出圖片寬高,最終執行到onSizeReay()方法中的engine.load()方法。
-
engine是一個負責加載和管理緩存資源的類.
Glide主要問題
- Glide的作用
Glide是Android中的一個圖片加載庫,用於實現圖片加載。
- 這個庫的優缺點
優點:
-
多樣化媒體加載:不僅可以進行圖片緩存,還支持Gif、WebP、縮略圖,甚至是Video。
-
通過設置綁定生命週期:可以使加載圖片的生命週期動態管理起來。
-
高效的緩存策略:支持內存、Disk緩存,並且Picasso只會緩存原始尺寸的圖片,內Glide緩存的是多種規格,也就是Glide會根據你ImageView的大小來緩存相應大小的圖片尺寸。
-
內存開銷小:默認的Bitmap格式是RGB_565格式,而Picasso默認的是ARGB_8888格式,內存開銷小一半。
缺點:
- 庫比較大,源碼實現複雜。
- 如何加載大圖並防止OOM ?
由於Android對圖片使用內存有限制,若是加載幾兆的大圖片便內存溢出。Bitmap會將圖片的所有像素(即長x寬)加載到內存中,如果圖片分辨率過大,會直接導致內存OOM,只有在BitmapFactory加載圖片時使用BitmapFactory.Options對相關參數進行配置來減少加載的像素。
BitmapFactory.Options相關參數詳解:
- inJustDecodeBounds:將這個參數的inJustDecodeBounds屬性設置爲true就可以讓解析方法禁止爲bitmap分配內存,返回值也不再是一個Bitmap對象,而是null。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值。這個技巧讓我們可以在加載圖片之前就獲取到圖片的長寬值和MIME類型,從而根據情況對圖片進行壓縮
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
- Options.inPreferredConfig值來降低內存消耗。
比如:默認值ARGB_8888改爲RGB_565,節約一半內存。
- 設置Options.inSampleSize 縮放比例,對大圖片進行壓縮 。
計算:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源圖片的高度和寬度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 計算出實際寬高和目標寬高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 選擇寬和高中最小的比率作爲inSampleSize的值,這樣可以保證最終圖片的寬和高
// 一定都會大於等於目標的寬和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
首先你要將BitmapFactory.Options的inJustDecodeBounds屬性設置爲true,解析一次圖片。然後將BitmapFactory.Options連同期望的寬度和高度一起傳遞到到calculateInSampleSize方法中,就可以得到合適的inSampleSize值了。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析將inJustDecodeBounds設置爲true,來獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 調用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用獲取到的inSampleSize值再次解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
- 設置Options.inPurgeable和inInputShareable:讓系統能及時回收內存。
A:inPurgeable:設置爲True時,表示系統內存不足時可以被回收,設置爲False時,表示不能被回收。
B:inInputShareable:設置是否深拷貝,與inPurgeable結合使用,inPurgeable爲false時,該參數無意義。
- 使用decodeStream代替decodeResource等其他方法。
- Fresco與Glide的對比:
- Glide:相對輕量級,用法簡單優雅,支持Gif動態圖,適合用在那些對圖片依賴不大的App中。
- Fresco:採用匿名共享內存來保存圖片,也就是Native堆,有效的的避免了OOM,功能強大,但是庫體積過大,適合用在對圖片依賴比較大的App中。
Glide三級緩存
常規三級緩存的流程:強引用->軟引用->硬盤緩存
當我們的APP中想要加載某張圖片時,先去LruCache中尋找圖片,如果LruCache中有,則直接取出來使用,如果LruCache中沒有,則去SoftReference中尋找(軟引用適合當cache,當內存吃緊的時候纔會被回收。而weakReference在每次system.gc()就會被回收)(當LruCache存儲緊張時,會把最近最少使用的數據放到SoftReference中),如果SoftReference中有,則從SoftReference中取出圖片使用,同時將圖片重新放回到LruCache中,如果SoftReference中也沒有圖片,則去硬盤緩存中中尋找,如果有則取出來使用,同時將圖片添加到LruCache中,如果沒有,則連接網絡從網上下載圖片。圖片下載完成後,將圖片保存到硬盤緩存中,然後放到LruCache中。
Glide的三層緩存機制
Glide緩存機制大致分爲:內存緩存(LURCache+弱引用)、磁盤緩存。
- 取的順序是:內存、弱引用、磁盤。
- 存的順序是:弱引用、內存、磁盤。
Glide的圖片加載過程中會調用兩個方法來獲取內存緩存,loadFromCache()和loadFromActiveResources()。這兩個方法中一個使用的就是LruCache算法,另一個使用的就是弱引用。
需要一個圖片資源,如果Lrucache中有相應的資源圖片,那麼就返回,同時從Lrucache中清除,放到activeResources(activeResources就是一個弱引用的HashMap)中。activeResources map是盛放正在使用的資源,以弱引用的形式存在。同時資源內部有被引用的記錄。如果資源沒有引用記錄了,那麼再放回Lrucache中,同時從activeResources中清除。如果Lrucache中沒有,就從activeResources中找,找到後相應資源引用加1。如果Lrucache和activeResources中沒有,走磁盤緩存。讀磁盤緩存分爲兩種情況,一種是調用decodeFromCache()方法從硬盤緩存當中讀取圖片,一種是調用decodeFromSource()來讀取原始圖片。默認情況下Glide會優先從緩存當中讀取,只有緩存中不存在要讀取的圖片時,纔會去讀取原始圖片。
如果緩存都讀不到,
那麼進行資源異步請求(網絡/文件),請求成功後,資源放到diskLrucache和activeResources中。
Glide源碼機制的核心思想:
使用一個弱引用map activeResources來盛放項目中正在使用的資源。Lrucache中不含有正在使用的資源。資源內部有個計數器來顯示自己是不是還有被引用的情況。把正在使用的資源和沒有被使用的資源分開有什麼好處呢?因爲當Lrucache需要移除一個緩存時,會調用resource.recycle()方法。注意到該方法上面註釋寫着只有沒有任何consumer引用該資源的時候纔可以調用這個方法。那麼爲什麼調用resource.recycle()方法需要保證該資源沒有任何consumer引用呢?glide中resource定義的recycle()要做的事情是把這個不用的資源(假設是bitmap或drawable)放到bitmapPool中。bitmapPool是一個bitmap回收再利用的庫,在做transform的時候會從這個bitmapPool中拿一個bitmap進行再利用。這樣就避免了重新創建bitmap,減少了內存的開支。而既然bitmapPool中的bitmap會被重複利用,那麼肯定要保證回收該資源的時候(即調用資源的recycle()時),要保證該資源真的沒有外界引用了。這也是爲什麼glide花費那麼多邏輯來保證Lrucache中的資源沒有外界引用的原因。
Android中軟引用與弱引用的應用場景。
在 Android 應用的開發中,爲了防止內存溢出,在處理一些佔用內存大而且生命週期較長的對象時候,可以儘量應用軟引用和弱引用技術。
- 1、軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列可以得知被回收的軟/弱引用的對象列表,從而爲緩衝器清除已失效的軟 / 弱引用。
- 2、如果只是想避免 OOM 異常的發生,則可以使用軟引用。如果對於應用的性能更在意,想盡快回收一些佔用內存比較大的對象,則可以使用弱引用。
- 3、可以根據對象是否經常使用來判斷選擇軟引用還是弱引用。如果該對象可能會經常使用的,就儘量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。
Android裏的內存緩存和磁盤緩存實現
內存緩存基於LruCache實現,磁盤緩存基於DiskLruCache實現。這兩個類都基於Lru算法和LinkedHashMap來實現。
LruCache原理
其實LRU緩存的實現類似於一個特殊的棧,把訪問過的元素放置到棧頂(若棧中存在,則更新至棧頂;若棧中不存在則直接入棧),然後如果棧中元素數量超過限定值,則刪除棧底元素(即最近最少使用的元素)。
它的內部存在一個 LinkedHashMap 和 maxSize,把最近使用的對象用強引用存儲在 LinkedHashMap 中,給出來 put 和 get 方法,每次 put 圖片時計算緩存中所有圖片的總大小,跟 maxSize 進行比較,大於 maxSize,就將最久添加的圖片移除,反之小於 maxSize 就添加進來。
LruCache的原理就是利用LinkedHashMap持有對象的強引用,按照Lru算法進行對象淘汰。具體說來假設我們從表尾訪問數據,在表頭刪除數據,當訪問的數據項在鏈表中存在時,則將該數據項移動到表尾,否則在表尾新建一個數據項。當鏈表容量超過一定閾值,則移除表頭的數據。
詳細來說就是LruCache中維護了一個集合LinkedHashMap,該LinkedHashMap是以訪問順序排序的。當調用put()方法時,就會在集合中添加元素,並調用trimToSize()判斷緩存是否已滿,如果滿了就用LinkedHashMap的迭代器刪除隊頭元素,即近期最少訪問的元素。當調用get()方法訪問緩存對象時,就會調用LinkedHashMap的get()方法獲得對應集合元素,同時會更新該元素到隊尾。
DisLruCache原理
DiskLruCache與LruCache原理相似,只是多了一個journal文件來做磁盤文件的管理
Bitmap壓縮策略
BitmapFactory.options 參數:
- inSampleSize 採樣率,對圖片高和寬進行縮放,以最小比進行縮放(一般取值爲 2 的指數)。通常是根據圖片寬高實際的大小/需要的寬高大小,分別計算出寬和高的縮放比。但應該取其中最小的縮放比,避免縮放圖片太小,到達指定控件中不能鋪滿,需要拉伸從而導致模糊。
- inJustDecodeBounds 獲取圖片的寬高信息,交給 inSampleSize 參數選擇縮放比。通過 inJustDecodeBounds = true,然後加載圖片就可以實現只解析圖片的寬高信息,並不會真正的加載圖片,所以這個操作是輕量級的。當獲取了寬高信息,計算出縮放比後,然後在將 inJustDecodeBounds = false,再重新加載圖片,就可以加載縮放後的圖片。
高效加載 Bitmap 的流程:
- 1、將 BitmapFactory.Options 的 inJustDecodeBounds 參數設爲 true 並加載圖片
- 2、從 BitmapFactory.Options 中取出圖片原始的寬高信息,對應於 outWidth 和 outHeight 參數
- 3、根據採樣率規則並結合目標 view 的大小計算出採樣率 inSampleSize
- 4、將 BitmapFactory.Options 的 inJustDecodeBounds 設置爲 false 重新加載圖片