圖片加載框架Glide簡單使用和緩存原理

基本使用流程

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框架圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hckoe3eR-1588411498138)(http://note.youdao.com/yws/res/3752/70AD6E9465804798B889A8AE4171EE76)]

GlideApp.with(context)

  1. 初始化各式各樣的配置信息(包括緩存,請求線程池,大小,圖片格式等等)以及glide對象。
  2. 將glide請求和application/SupportFragment/Fragment的生命週期綁定在一塊。

with(context)加載流程圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-p4JiWPQH-1588411498140)(http://note.youdao.com/yws/res/3755/WEBRESOURCE4f989e6cdae57aac2b05900557710aa2)]

load(url)

設置請求url,並記錄url已設置的狀態。

into(iv)

  1. 首先根據轉碼類transcodeClass類型返回不同的ImageViewTarget:BitmapImageViewTarget、DrawableImageViewTarget。

  2. 遞歸建立縮略圖請求,沒有縮略圖請求,則直接進行正常請求。

  3. 如果沒指定寬高,會根據ImageView的寬高計算出圖片寬高,最終執行到onSizeReay()方法中的engine.load()方法。

  4. engine是一個負責加載和管理緩存資源的類.

源碼分析

Glide主要問題

  1. Glide的作用

Glide是Android中的一個圖片加載庫,用於實現圖片加載。

  1. 這個庫的優缺點

優點:

  • 多樣化媒體加載:不僅可以進行圖片緩存,還支持Gif、WebP、縮略圖,甚至是Video。

  • 通過設置綁定生命週期:可以使加載圖片的生命週期動態管理起來。

  • 高效的緩存策略:支持內存、Disk緩存,並且Picasso只會緩存原始尺寸的圖片,內Glide緩存的是多種規格,也就是Glide會根據你ImageView的大小來緩存相應大小的圖片尺寸。

  • 內存開銷小:默認的Bitmap格式是RGB_565格式,而Picasso默認的是ARGB_8888格式,內存開銷小一半。

缺點:

  • 庫比較大,源碼實現複雜。
  1. 如何加載大圖並防止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等其他方法。
  1. 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中軟引用與弱引用的應用場景。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NJ0AORTW-1588411498142)(http://note.youdao.com/yws/res/3791/A35920BABB00467E8F504039D94419B3)]

在 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 重新加載圖片
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章