Q1:看過Glide源碼嗎,你印象最深的是什么奠货?
Glide緩存簡介
Glide的緩存設(shè)計可以說是非常先進的壤玫,考慮的場景也很周全。在緩存這一功能上历筝,Glide又將它分成了兩個模塊,一個是內(nèi)存緩存誓斥,一個是硬盤緩存薪者。
這兩個緩存模塊的作用各不相同,內(nèi)存緩存的主要作用是防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當中玉吁,而硬盤緩存的主要作用是防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)照弥。
內(nèi)存緩存和硬盤緩存的相互結(jié)合才構(gòu)成了Glide極佳的圖片緩存效果,那么接下來我們就分別來分析一下這兩種緩存的使用方法以及它們的實現(xiàn)原理诈茧。
Q2:簡單說一下Glide的三級緩存产喉?
相關(guān)文章:Glide源碼學(xué)習(xí)四:緩存
Android圖片加載框架最全解析(三),深入探究Glide的緩存機制
【Android - 進階】之圖片三級緩存的原理及實現(xiàn)
Android Glide緩存策略分析
簡單描述:
讀取的順序是:Lru算法緩存敢会、弱引用緩存曾沈、磁盤緩存
寫入的順序是:弱引用緩存、Lru算法緩存鸥昏、磁盤緩存(不準確)
下面敘述一下三級緩存的流程:(【Android - 進階】之圖片三級緩存的原理及實現(xiàn))
當我們的APP中想要加載某張圖片時塞俱,先去LruCache中尋找圖片,如果LruCache中有吏垮,則直接取出來使用障涯,如果LruCache中沒有,則去WeakReference中尋找膳汪,如果WeakReference中有唯蝶,則從WeakReference中取出圖片使用,同時將圖片重新放回到LruCache中遗嗽,如果WeakReference中也沒有圖片粘我,則去文件系統(tǒng)中尋找,如果有則取出來使用痹换,同時將圖片添加到LruCache中征字,如果沒有都弹,則連接網(wǎng)絡(luò)從網(wǎng)上下載圖片。圖片下載完成后匙姜,將圖片保存到文件系統(tǒng)中畅厢,然后放到LruCache中。
嚴格來講氮昧,并沒有什么Glide的三級緩存框杜,因為Glide的緩存只有兩個模塊,一個是內(nèi)存緩存郭计,一個是磁盤緩存霸琴。其中內(nèi)存緩存又分為Lru算法的緩存和弱引用緩存。
LruCache算法昭伸,Least Recently Used梧乘,又稱為近期最少使用算法。主要算法原理就是把最近所使用的對象的強引用存儲在LinkedHashMap上庐杨,并且选调,把最近最少使用的對象在緩存池達到預(yù)設(shè)值之前從內(nèi)存中移除。
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true);
}
弱引用緩存:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
//弱引用緩存
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
activeResources = new HashMap<Key, WeakReference<EngineResource<?>>>();
}
Glide緩存機制大致分為三層:Lru算法緩存灵份、弱引用緩存仁堪、磁盤緩存。
讀取的順序是:Lru算法緩存填渠、弱引用緩存弦聂、磁盤緩存
寫入的順序是:弱引用緩存、Lru算法緩存氛什、磁盤緩存(不準確)
我們先來看讀容汉:Lru算法緩存、弱引用緩存枪眉、磁盤緩存
首先 memoryCache的初始值是一個LruResourceCache對象捺檬,即默認是lru算法的緩存。
GlideBuilder.java
private MemoryCache memoryCache;
Glide createGlide() {
memoryCache = new LruResourceCache(calculator.getMemoryCacheSize())贸铜;
}
Engine.java
private final MemoryCache cache;
public <T, Z, R> LoadStatus load(...){
...
//獲取Lru算法的緩存堡纬,如果沒有,就從弱引用中獲取緩存
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
...
//從弱引用中獲取緩存
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
...
//從磁盤中獲取緩存
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height,
fetcher, loadProvider, transformation,transcoder,
diskCacheProvider, diskCacheStrategy, priority);
}
接下來我們看寫入:弱引用緩存蒿秦、Lru算法緩存烤镐、磁盤緩存
內(nèi)存緩存的寫入:
EngineResource是用一個acquired(int)變量用來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會讓變量加1棍鳖,
調(diào)用release()方法會讓變量減1职车。
acquired變量大于0的時候,說明圖片正在使用中,也就應(yīng)該放到activeResources弱引用緩存當中悴灵;
如果acquired變量等于0了,說明圖片已經(jīng)不再被使用了骂蓖,那么此時會調(diào)用方法來釋放資源积瞒,
這里首先會將緩存圖片從activeResources中移除,然后再將它put到LruResourceCache當中登下。
這樣也就實現(xiàn)了正在使用中的圖片使用弱引用來進行緩存茫孔,不在使用中的圖片使用LruCache來進行緩存的功能
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
jobs.remove(key);
}
}
現(xiàn)在就非常明顯了,可以看到被芳,在第13行缰贝,回調(diào)過來的EngineResource被put到了activeResources當中,也就是在這里寫入的緩存畔濒。
那么這只是弱引用緩存剩晴,還有另外一種LruCache緩存是在哪里寫入的呢?這就要介紹一下EngineResource中的一個引用機制了侵状。觀察剛才的handleResultOnMainThread()方法赞弥,在第15行和第19行有調(diào)用EngineResource的acquire()方法,在第23行有調(diào)用它的release()方法趣兄。其實绽左,EngineResource是用一個acquired變量用來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會讓變量加1艇潭,調(diào)用release()方法會讓變量減1拼窥,代碼如下所示:
class EngineResource<Z> implements Resource<Z> {
private int acquired;
...
void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call acquire on the main thread");
}
++acquired;
}
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
}
也就是說,當acquired變量大于0的時候蹋凝,說明圖片正在使用中鲁纠,也就應(yīng)該放到activeResources弱引用緩存當中。而經(jīng)過release()之后仙粱,如果acquired變量等于0了房交,說明圖片已經(jīng)不再被使用了,那么此時會在第24行調(diào)用listener的onResourceReleased()方法來釋放資源伐割,這個listener就是Engine對象候味,我們來看下它的onResourceReleased()方法:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
可以看到,這里首先會將緩存圖片從activeResources中移除隔心,然后再將它put到LruResourceCache當中白群。這樣也就實現(xiàn)了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存的功能硬霍。
這就是Glide內(nèi)存緩存的實現(xiàn)原理帜慢。
Q3:Glide加載一個一兆的圖片(100100),是否會壓縮后再加載,放到一個200200的view上會怎樣粱玲,1000*1000呢躬柬,圖片會很模糊,怎么處理抽减?
而使用Glide允青,我們就完全不用擔心圖片內(nèi)存浪費,甚至是內(nèi)存溢出的問題卵沉。因為Glide從來都不會直接將圖片的完整尺寸全部加載到內(nèi)存中颠锉,而是用多少加載多少。Glide會自動判斷ImageView的大小史汗,然后只將這么大的圖片像素加載到內(nèi)存當中琼掠,幫助我們節(jié)省內(nèi)存開支。
ImageView默認的scaleType是FIT_CENTER
FitCenter的效果:會將圖片按照原始的長寬比充滿全屏
那么停撞,Glide將圖片壓縮到什么程度呢瓷蛙?.
關(guān)于圖片加載神器--Glide與Picasso的使用與比較
https://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en
在默認情況下Picasso和Glide的外部緩存機制是非常不一樣的,通過實驗可以發(fā)現(xiàn)(1920x1080 像素的圖片被加載到768x432像素的imageview中)怜森,Glide緩存的是768x432像素的圖片速挑,而Picasso緩存的是整張圖片(1920x1080像素)。
當我們調(diào)整imageview的大小時副硅,Picasso會不管imageview大小是什么姥宝,總是直接緩存整張圖片,而Glide就不一樣了恐疲,它會為每個不同尺寸的Imageview緩存一張圖片腊满,也就是說不管你的這張圖片有沒有加載過,只要imageview的尺寸不一樣培己,那么Glide就會重新加載一次碳蛋,這時候,它會在加載的imageview之前從網(wǎng)絡(luò)上重新下載省咨,然后再緩存肃弟。
防止各位不明白,再來舉個例子零蓉,如果一個頁面的imageview是200200像素笤受,而另一個頁面中的imageview是100100像素,這時候想要讓兩個imageview像是同一張圖片敌蜂,那么Glide需要下載兩次圖片箩兽,并且緩存兩張圖片。
Q4:Glide 緩存原理章喉,如何設(shè)計一個大圖加載框架汗贫。
如何實現(xiàn)一個圖片加載框架
概括來說身坐,圖片加載包含封裝,解析落包,下載部蛇,解碼,變換咐蝇,緩存搪花,顯示等操作。
流程圖如下:
Q5:LRUCache 原理嘹害。
LRUCache源碼分析
LruCache算法,又稱為近期最少使用算法吮便。主要算法原理就是把最近所使用的對象的強引用存儲在LinkedHashMap上笔呀,并且,把最近最少使用的對象在緩存池達到預(yù)設(shè)值之前從內(nèi)存中移除髓需。
Q6:Glide VS Picasso
雙胞胎兄弟之間的對比许师,使用方式相同,但 Glide 之所以勝出僚匆,不僅僅是 Google的推薦微渠,更多應(yīng)該歸功于 GIF 的支持。 在沒有 Glide 之前咧擂,常用的做法就是寫了個自定義 view 然后 用一個 media 去播放逞盆。有了 Glide 之后幾乎對于 GIF 無感知了的, 內(nèi)部已經(jīng)支持了的松申≡坡可以像普通圖片那樣去加載并且顯示出來動圖。
安卓圖片顯示的質(zhì)量配置主要分為四種:
ARGB_8888 :32位圖,帶透明度,每個像素占4個字節(jié)
ARGB_4444 :16位圖,帶透明度,每個像素占2個字節(jié)
RGB_565 :16位圖,不帶透明度,每個像素占2個字節(jié)
ALPHA_8 :32位圖,只有透明度,不帶顏色,每個像素占4個字節(jié)
(A代表透明度,RGB代表紅綠藍:即顏色)
Picasso的默認質(zhì)量是 ARGB_8888
Glide的默認質(zhì)量則為 RGB_565
加載一張4000 * 2000(一般手機拍攝的都超過這個像素)的圖片
Picasso需要占用的內(nèi)存為: 32MB
4000 * 2000 * 4 / 1024 / 1024 = 30 (MB)
Glide需要占用的內(nèi)存為: 16MB
4000 * 2000 * 2 / 1024 / 1024 = 15 (MB)
也就是說只要同時加載幾張圖片,你的應(yīng)用就會OOM(內(nèi)存溢出了),最恐怖的是就算你的ImageView的寬高只有10px,同樣會占用那么多內(nèi)存,這就是為什么需要做圖片壓縮的原因了
Q7:Glide VS fresco
兩個都支持 GIF贸桶。所以 GIF 這一關(guān)pass掉舅逸。說到這里不得不提到一個頭疼的OOM問題,fresco 之所以很快闖入大家的視線皇筛,大概就是因為 Facebook 說他們使用了 native 內(nèi)存規(guī)避掉了 OutOfMemoryError 問題琉历。而且官方還專門寫了個demo,把幾大流行的開源庫都集成進去水醋,為了說明自己的圖片加載庫加載同樣的圖片速度更快旗笔,內(nèi)存占用更低。所以 fresco 相比較于 Glide 的(官方)優(yōu)勢就是這兩點: 內(nèi)存以及加載速度离例。但是我為什么依舊堅持拋棄了 fresco ?
“ In Android 4.x and lower, Fresco puts images in a special region of Android memory. This lets your application run faster - and suffer the dreaded OutOfMemoryError much less often.” 官方的原話是這么說的换团,所以在高版本上面依舊使用的Java 內(nèi)存,所以不可避免依舊會占用內(nèi)存宫蛆。
提到內(nèi)存艘包,不得不說到另外一個笑話的猛,fresco 最大只支持圖片文件大小為 2M 。記得有一次幫其他團隊跟蹤問題想虎,看到了 fresco 源碼中有一個 最大 size 2M 常量 卦尊。于是當場找了一個10M的圖片作為測試。 Glide 正常顯示舌厨, fresco顯示黑屏岂却。。裙椭。
使用方式上躏哩,fresco 推薦的是用他提供的 SimpleDraweeView . 這個方式意味著我們的遷移成本會非常的高,要改布局文件揉燃,其次還必須給定大猩ǔ摺(或者比例)。 當然他也支持代碼來加載圖片炊汤,比如 DraweeHierarchy正驻,但是寫起來還是真心很費勁的,很不友好抢腐,改動成本居高姑曙。
fresco 更多是native實現(xiàn)。所以需要對NDK有所了解迈倍,但個人對NDK不太了解伤靠,相比較于 Glide, 同樣遇到問題之后授瘦,修改源碼的成本醋界,Glide 成本更可控。前者可能就不太好下手了的提完。
Glide 各種 BitmapTransformation形纺,比如圓形,圓角等徒欣,更讓人喜歡逐样。
這一點就當隨意吐槽一下,當然也可以說心疼一下 Facebook打肝。因為在沒有 Android studio (gradle構(gòu)建)的情況下脂新,想必大家都用的是 eclipse 吧。那么就意味著 fresco 得提供 Jar 包. 這一點當時也是把很多人拒之門外了的粗梭,可笑的是當 Facebook 費了老大勁的搞出來 jar 包之后争便,大家早就紛紛轉(zhuǎn)戰(zhàn) gradle 構(gòu)建工程, 直接 maven 依賴啦。大寫的尷尬断医。