原文連接:Glide核心設計二:緩存管理java
Glide做爲一個優秀的圖片加載框架,緩存管理是必不可少的一部分,這篇文章主要經過各個角度、從總體設計到代碼實現,深刻的分析Glide的緩存管理模塊,力求在同類分析Glide緩存的分析文章中脫穎而出。關於Glide的生命週期綁定,可查看Glide系列文章Glide核心設計一:皮皮蝦,咱們走。git
Glide的緩存類型分爲兩大類,一類是Resource緩存,一類是Bitmap緩存。github
爲何須要緩存圖片Resource,很好理解,由於圖片從網絡加載,將圖片緩存到本地,當須要再次使用時,直接從緩存中取出而無需再次請求網絡。算法
Glide在緩存Resource使用三層緩存,包括:canvas
Bitmap所佔的內存大小由三部分組成:圖片的寬度分辨率、高度分辨率和Bitmap質量參數。公式是:Bitmap內存大小 = (寬pix長pix)質量參數所佔的位數。單位是字節B。設計模式
質量參數決定每個像素點用多少位(bit)來顯示:緩存
Glide默認使用RGB_565,比系統默認使用的ARGB_8888節省一半的資源,但RGB_565沒法顯示透明度。
舉個例子:在手機上顯示100pix*200pix的圖片,解壓前15KB,是使用Glide加載(默認RGB_565)Bitmap所佔用的內存是:(100x200)x2B = 40000B≈40Kb,比以文件的造成存儲的增長很多,由於png、jpg等格式的圖片通過壓縮。正由於Bitmap比較消耗內存,例如使用Recyclerview等滑動控件顯示大量圖片時,將大量的建立和回收Bitmap,致使內存波動影響性能。性能優化
在Glide中,使用BitmapPool來緩存Bitmap,使用的也是LRU算法。當須要使用Bitmap時,從Bitmap的池子中取出合適的Bitmap,若取不到合適的,則再新建立。當Bitmap使用完後,不直接調用Bitmap.recycler()回收,而是放入Bitmap的池子。網絡
Glide的緩存使用
Resource包括三層緩存,經過流程圖看它們之間的關係:
不管是Resource仍是Bitmap緩存,若顯示的僅是部分照片,而且不存在頻繁使用的場景,則使用Glide沒有太大的優點。設計緩存的目的就是爲了在重複顯示時,更快、更省的顯示圖片資源。Glide有針對ListView、Recyclerview等控件加載多圖時進行優化。此處討論最多見的場景:Recyclerview顯示多圖,簡略圖以下。
BitmapPool的LRU算法流程圖以下:
在進行代碼分析前,先給出跟Glide緩存管理相關的類圖(省略類的大部分變量和方法)。
根據以上的Glide緩存管理的結論及類圖,可自主跟源碼,跳過如下內容。
返回RequestManager,主要實現和Fragment、Activity生命週期的綁定,詳情請看Glide核心設計一:皮皮蝦,咱們走。
RequestManager的load(String)方法返回DrawableTypeRequest,根據圖片地址返回一個用於建立圖片請求的Request的Builder,代碼以下:
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string); //調用fromString()和load()方法
}複製代碼
fromString()方法調用loadGeneric()方法,代碼以下:
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
Glide.buildFileDescriptorModelLoader(modelClass, context);
if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
+ " which there is a registered ModelLoader, if you are using a custom model, you must first call"
+ " Glide#register with a ModelLoaderFactory for your custom model class");
}
return optionsApplier.apply( //傳遞的參數中建立了一個DrawableTypeRequest並返回該對象
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}複製代碼
DrawableTypeRequest的load()方法以下:
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}複製代碼
DrawableTypeRequest父類是DrawableRequestBuilder,父類的父類是GenericRequestBuilder,調用super.load()方法以下:
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
this.model = model;
isModelSet = true;
return this;
}複製代碼
以上代碼可知,緩存管理的主要實現代碼並不在.load(Sting)代碼,接下來繼續分析.into(ImageView)代碼。
GenericRequestBuilder的into(ImageView)代碼以下:
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) { //根據圖片的scaleType作相應處理
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
//調用buildImageViewTarget()方法建立了一個Target類型的對象
return into(glide.buildImageViewTarget(view, transcodeClass));
}複製代碼
以上代碼主要有兩個功能:
繼續查看into(Target)的代碼:
public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}
Request previous = target.getRequest(); //獲取請求體Request
if (previous != null) { //若ImageView是複用過的,則previous不爲空
previous.clear(); //調用clear()方法清空ImageView上的圖片資源,此方法會將回收的Resource放入內存緩存中,並不在內存中清空該資源。
requestTracker.removeRequest(previous); //移除老的請求
previous.recycle(); //回收Request使用
}
Request request = buildRequest(target); //獲取新的Request
target.setRequest(request); //將新的request設置到target中
lifecycle.addListener(target); //添加生命週期的監聽
requestTracker.runRequest(request); //啓動Request
return target;
}複製代碼
以上代碼,主要將圖片加載的Request綁定到Target中,若原有Target具備舊的Request,得先處理舊的Request,再綁定上新的Request。target.setRequest()和target.getRequest()最終會調用ViewTarget的setRequest()方法和getRequest()方法,代碼以下:
public void setRequest(Request request) {
setTag(request);
}
private void setTag(Object tag) {
if (tagId == null) {
isTagUsedAtLeastOnce = true;
view.setTag(tag);//調用view的setTag方法,將Request和view作綁定
} else {
view.setTag(tagId, tag);//調用view的setTag方法,將Request和view作綁定
}
}
public Request getRequest() {
Object tag = getTag(); //獲取view 的tag
Request request = null;
if (tag != null) {
if (tag instanceof Request) { //若該tag是Request的一個實例
request = (Request) tag;
} else { //用戶不能給view設置tag,由於該view的tag要用於保存Glide的Request對象,不然拋出異常
throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");
}
}
return request;
}複製代碼
以上代碼可知,Request經過setTag的方式和View進行綁定,當View是複用時,則Request不爲空,經過Request可對原來的資源進行緩存與回收。此處經過View的setTag()方法綁定Request,可謂妙用。
以上代碼建立了一個Request,requestTracker.runRequest(request);啓動了Request,調用Request的begin()方法,該Request實例是GenericRequest,begin()代碼以下:
@Override
public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}
status = Status.WAITING_FOR_SIZE; //設置等待圖片size的寬高狀態
if (Util.isValidDimensions(overrideWidth, overrideHeight)) { //必需要肯定圖片的寬高,肯定了則調用onSizeReady
onSizeReady(overrideWidth, overrideHeight);
} else { //設置回調,監聽界面的繪製,當檢測到寬高有效時,回調onSizeReady方法
target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}複製代碼
加載圖片前,必需要肯定圖片的寬高,由於須要根據肯定的寬高來獲取資源。onSizeReady代碼以下:
@Override
public void onSizeReady(int width, int height) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {//寬高沒準備好,返回
return;
}
status = Status.RUNNING; //狀態改成加載運行中
width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);
ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
if (dataFetcher == null) {
onException(new Exception("Failed to load model: \'" + model + "\'"));
return;
}
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadedFromMemoryCache = true;
//真正的加載任務交給engine
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
loadedFromMemoryCache = resource != null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}複製代碼
以上代碼可知,在肯定寬高後,將圖片加載的任務交給類型爲Engine的對象engine,並調用其load方法,代碼以下:
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
final String id = fetcher.getId(); //該id爲圖片的網絡地址
//緩存key的組成部分,使用工廠模式
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
//使用一級緩存,從回收的內存緩存中查找EngineResource
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) { //命中則直接返回
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//從二級緩存中查找
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {//命中則直接返回
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineJob current = jobs.get(key);
if (current != null) {//該任務已經在執行,只須要添加回調接口,在任務執行完後調用接口告知便可
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
//一級緩存和二級緩存都不命中的狀況下,啓動新的任務
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);//建立EngineJob
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority); //建立DecodeJob
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable); //啓動EngineRunnable runnable,使用線程池FifoPriorityThreadPoolExecutor管理
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}複製代碼
分析至此,咱們終於看到實現一級緩存和二級緩存的相關代碼,能夠猜想三級緩存的實現跟EngineRunnable有關。engineJob.start(runnable)會啓動EngineRunnable的start()方法。代碼以下:
@Override
public void run() {
if (isCancelled) {
return;
}
Exception exception = null;
Resource<?> resource = null;
try {
resource = decode(); //調用decode()方法
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Exception decoding", e);
}
exception = e;
}
if (isCancelled) { //請求被取消
if (resource != null) {
resource.recycle();
}
return;
}
if (resource == null) { //加載失敗
onLoadFailed(exception);
} else { //加載成功
onLoadComplete(resource);
}
}複製代碼
查看decode()方法以下:
private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
return decodeFromCache(); //從磁盤緩存中獲取
} else {
return decodeFromSource(); //從網絡中獲取資源
}
}複製代碼
至此,咱們看到磁盤緩存和網絡請求獲取圖片資源的代碼。查看onLoadFailed()的代碼邏輯可知,默認先從磁盤獲取,失敗則從網絡獲取。
以上就是Resource三層緩存的代碼,接下來看BitmapPool的緩存實現代碼。
在decodeFromSource()的代碼中,會返回一個類型爲BitmapResource的對象。在RecyclerView的例子中,當ImageView被複用時,會在Tag中取出Request,調用request.clear()代碼。該方法最終會調用BitmapResource的recycler()方法,代碼以下:
public void recycle() {
if (!bitmapPool.put(bitmap)) {
bitmap.recycle();
}
}複製代碼
該代碼調用bitmapPool.put(bitmap),bitmapPool的實例是LruBitmapPool代碼以下:
public synchronized boolean put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || !allowedConfigs.contains(bitmap.getConfig())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Reject bitmap from pool"
+ ", bitmap: " + strategy.logBitmap(bitmap)
+ ", is mutable: " + bitmap.isMutable()
+ ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));
}
return false;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);//該strategy的實例是Lru算法
tracker.add(bitmap); //log跟蹤
puts++; //緩存的bitmap數量標記加一
currentSize += size;//緩存bitmap的總大小
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump(); //僅用於Log
evict(); //判斷是否超出指定的內存大小,若超出則移除
return true;
}複製代碼
能夠看出,正常狀況下調用put方法返回true,證實緩存該Bitmap成功,緩存成功則不調用bitmap.recycler()方法。當須要使用Bitmap時,先從Bitmap中查找是否有符合條件的Bitmap。在RecyclerView中使用Glide的例子中,將大量複用寬高及Bitmap.Config都相等的Bitmap,極大的優化系統內存性能,減小頻繁的建立回收Bitmap。
Glide的緩存管理至此就分析完了,主要抓住Resource和Bitmap的緩存來說解。在代碼的閱讀中還發現了工廠、裝飾者等設計模式。Glide的解耦給開發者提供很大的便利性,可根據自身需求設置緩存參數,例如默認Bitmap.Config、BitmapPool緩存大小等。最後,針對Glide的緩存設計,提出幾點小建議: