在早期的Android開發中,圖片加載其實一直是個比較麻煩的問題。咱們在處理圖片時會遇到各類各樣的問題:內存溢出、列表中圖片錯位等等。但到了現在,這些問題基本上是不會再遇到了。因爲不少的優秀的圖片加載框架幫咱們處理了圖片相關問題的痛點,因此如今Android中關於圖片加載的部分變得很是簡單。Android中最著名的圖片加載框架就是Glide了,咱們今天來深刻研究一下Glide的源碼。android
以Glide3.8.0版原本分析,咱們先看下最多見使用方法:算法
Glide.with(fragment)
.load(myUrl)
.into(imageView);
複製代碼
上面的代碼是咱們很是熟悉的Glide的基本用法,分爲3個步驟:緩存
看一下with(context)d的源碼:bash
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
public static RequestManager with(Activity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
public static RequestManager with(android.app.Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
public static RequestManager with(Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
複製代碼
能夠看到,with方法有不少,但內容基本一致,都是經過 RequestManagerRetriever.get();
獲取一個 RequestManagerRetriever 對象 retriever ,而後經過 retriever.get(context);
獲取一個 RequestManager 對象並返回。這些with方法關鍵的不一樣在於傳入的參數不一致,能夠是Context、Activity、Fragment等等。那麼爲何要分這麼多種呢?其實咱們應該都知道:Glide在加載圖片的時候會綁定 with(context) 方法中傳入的 context 的生命週期,若是傳入的是 Activity ,那麼在這個 Activity 銷燬的時候Glide會中止圖片的加載。這樣作的好處是顯而易見的:避免了消耗多餘的資源,也避免了在Activity銷燬以後加載圖片從而致使的空指針問題。網絡
爲了更好的分析 with(context) 中的這兩步,咱們來看一下 RequestManagerRetriever :app
public class RequestManagerRetriever implements Handler.Callback {
//餓漢式建立單例
private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
//返回單例對象
public static RequestManagerRetriever get() {
return INSTANCE;
}
//根據傳入的參數,獲取不一樣的RequestManager
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
//省略無關代碼......
}
複製代碼
很明顯,這是個餓漢式的單例模式。關鍵在於 retriever.get(context)
,咱們繼續看代碼:框架
//根據傳入的參數,獲取不一樣的RequestManager
public RequestManager get(Context context) {
//context爲null則拋出異常
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
//當前線程是主線程而且此context並非Application的實例,根據context的類型作不一樣的處理
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
//若是以上條件都不知足
return getApplicationManager(context);
}
複製代碼
上面這個方法主要是經過傳入context的不一樣類型來作不一樣的操做。context能夠是Application、FragmentActivity、Activity或者是ContextWrapper。咱們先看一下當context是Application時的操做:ide
private RequestManager getApplicationManager(Context context) {
// 返回一個單例
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
applicationManager = new RequestManager(context.getApplicationContext(),
new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
}
}
}
return applicationManager;
}
複製代碼
代碼應該都能看懂, getApplicationManager(Context context)
經過單例模式建立並返回了 applicationManager 。咱們再來看一下若是傳入的context是Activity時的操做:源碼分析
public RequestManager get(Activity activity) {
//若是不在主線程或者Android SDK的版本低於HONEYCOMB,傳入的仍是Application類型的context
if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return get(activity.getApplicationContext());
} else {
//判斷當前activity是否被銷燬
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
//經過fragmentGet(activity, fm)獲取RequestManager
return fragmentGet(activity, fm);
}
}
複製代碼
代碼邏輯很簡單:若是不在主線程或者Android SDK版本太低,走的仍是傳入Application的方法,這個方法在上面提到過;反之,首先判斷當前activity是否被銷燬,若是沒有被銷燬,則經過fragmentGet(activity, fm)獲取RequestManager。關鍵是這個 fragmentGet(activity, fm) ,咱們來看一下:fetch
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
//在當前activity中建立一個沒有界面的的fragment並add到當前activity中
RequestManagerFragment current = getRequestManagerFragment(fm);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
//建立一個requestManager
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
//將requestManager與fragment綁定
current.setRequestManager(requestManager);
}
return requestManager;
}
複製代碼
fragmentGet 這個方法主要是在當前activity中建立一個沒有界面的的fragment並add到當前activity中,以此來實現對activity生命週期的監聽。到此, with 方法已經基本介紹完畢了,作一下總結:
with(context)返回一個RequestManager,接下來咱們看一下RequestManger中的load(url)方法:
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
複製代碼
這個方法分兩步:fromString()、load(string),先看第一個方法:
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
複製代碼
這個方法返回的是 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");
}
//這句是核心,本質是建立並返回了一個DrawableTypeRequest
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
複製代碼
在 loadGeneric(Class modelClass) 方法中,咱們只須要關注核心便可。它的核心是最後一句,方法調用看着很複雜,其實本質是建立並返回了一個DrawableTypeRequest,Drawable類型的請求。再來看 load(string) 方法:
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}
複製代碼
須要注意的是這個方法存在於DrawableTypeRequest的父類DrawableRequestBuilder中,這個方法首先調用DrawableRequestBuilder的父類的load方法,而後返回自身。再看一下DrawableRequestBuilder父類中的load方法:
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
this.model = model;
isModelSet = true;
return this;
}
複製代碼
DrawableRequestBuilder的父類是GenericRequestBuilder,從名字中咱們也能夠看出來,前者是Drawable請求的構建者,後者是通用的請求構建者,他們是子父關係。這個load方法實際上是把咱們傳入的String類型的URL存入了內部的model成員變量中,再將數據來源是否已經設置的標誌位 isModelSet 設置爲true,意味着咱們在調用 Glide.with(context).load(url)
以後數據來源已經設置成功了。
說到這裏,其實Glide中的load(url)基本已經結束了,小夥伴們可能會有問題要問:我平時使用Glide會加一些配置,好比:
Glide.with(context)
.load(url)
.placeholder(R.drawable.place_image)
.error(R.drawable.error_image)
.into(imageView);
複製代碼
其實你們在寫的時候是會有一種感受的,這種寫法很像Builder模式。沒錯,這就是一個Builder模式。通過上面的分析咱們知道,在 Glide.with(context).load(url)
以後會返回一個DrawableTypeRequest的對象,它的父類是DrawableRequestBuilder,DrawableRequestBuilder的父類是GenericRequestBuilder,咱們寫的placeHolder()、error()等等相關圖片請求配置的方法都定義在GenericRequestBuilder中,咱們來簡單的看一下:
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
int resourceId) {
this.placeholderId = resourceId;
return this;
}
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> error(
int resourceId) {
this.errorId = resourceId;
return this;
}
複製代碼
是否是一會兒就明白了,咱們平時對圖片請求的配置使用的就是Builder模式。
簡單的說,Glide中的前兩步是建立了一個Request,這個Request能夠理解爲對圖片加載的配置請求,須要注意的是僅僅是建立了一個 請求 ,而並無去執行。在Glide的最後一步into方法中,這個請求才會真實的執行。
咱們來DrawableTypeRequest中找一下into方法,發現沒找到,那確定是在他的父類DrawableRequestBuilder中,咱們來看一下DrawableRequestBuilder中的into方法:
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
複製代碼
嗯,它調用的是父類GenericRequestBuilder的方法,那咱們繼續看GenericRequestBuilder的into方法:
public Target<TranscodeType> into(ImageView view) {
//確保在主線程
Util.assertMainThread();
//確保view不爲空
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
//對ScaleType進行配置
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
//核心
return into(glide.buildImageViewTarget(view, transcodeClass));
}
複製代碼
能夠看到,上面的方法就是into的核心代碼了,它定義在GenericRequestBuilder這個通用的請求構建者中。方法的核心是最後一行: into(glide.buildImageViewTarget(view, transcodeClass))
,首先是經過 glide.buildImageViewTarget(view, transcodeClass)
建立出一個 Target 類型的對象,而後把這個target傳入GenericRequestBuilder中的into方法中。咱們先來看一下Glide中的 buildImageViewTarget(view, transcodeClass) 方法:
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
複製代碼
這個方法的目的是把咱們傳入的imageView包裝成一個Target。內部調用了 imageViewTargetFactory.buildTarget(imageView, transcodedClass)
繼續跟進去看一下:
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
//圖片來源是GlideDrawable
if (GlideDrawable.class.isAssignableFrom(clazz)) {
//建立GlideDrawable對應的target
return (Target<Z>) new GlideDrawableImageViewTarget(view);
} else if (Bitmap.class.equals(clazz)) {
//若是圖片來源是Bitmap,建立Bitmap對應的target
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
//若是圖片來源是Drawable,建立Drawable對應的target
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException("Unhandled class: " + clazz
+ ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
複製代碼
這個方法的的本質是:經過對圖片來源類型的判斷,建立並返回與圖片來源對應的imageViewTarget。獲取到相應的target以後,咱們來看GenericRequestBuilder中的into方法:
public <Y extends Target<TranscodeType>> Y into(Y target) {
//確保在主線程
Util.assertMainThread();
//確保target不爲空
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
//確保數據來源已經肯定,即已經調用了load(url)方法
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}
//獲取當前target已經綁定的Request對象
Request previous = target.getRequest();
//若是當前target已經綁定了Request對象,則清空這個Request對象
if (previous != null) {
previous.clear();
//中止綁定到當前target的上一個Request的圖片請求處理
requestTracker.removeRequest(previous);
previous.recycle();
}
//建立Request對象
Request request = buildRequest(target);
//與target綁定
target.setRequest(request);
lifecycle.addListener(target);
//執行request
requestTracker.runRequest(request);
return target;
}
複製代碼
咱們梳理一下方法中的邏輯:
在沒有Glide以前,咱們處理ListView中的圖片加載實際上是一件比較麻煩的事情。因爲ListView中Item的複用機制,會致使網絡圖片加載的錯位或者閃爍。那咱們解決這個問題的辦法也很簡單,就是給當前的ImageView設置tag,這個tag能夠是圖片的URL等等。當從網絡中獲取到圖片時判斷這個ImageVIew中的tag是不是這個圖片的URL,若是是就加載圖片,若是不是則跳過。
在有了Glide以後,咱們處理ListView或者Recyclerview中的圖片加載就很無腦了,根本不須要做任何多餘的操做,直接正常使用就好了。這其中的原理是Glide給咱們處理了這些判斷,咱們來看一下Glide內部是如何處理的:
public Request getRequest() {
//本質仍是getTag
Object tag = getTag();
Request request = null;
if (tag != null) {
if (tag instanceof Request) {
request = (Request) tag;
} else {
throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");
}
}
return request;
}
@Override
public void setRequest(Request request) {
//本質是setTag
setTag(request);
}
複製代碼
能夠看到, target.getRequest()
和 target.setRequest(Request request)
本質上仍是經過setTag和getTag來作的處理,這也印證了咱們上面所說。
繼續回到into方法中,在建立並綁定了Request後,關鍵的就是 requestTracker.runRequest(request)
來執行咱們建立的請求了。
public void runRequest(Request request) {
//將請求加入請求集合
requests.add(request);
if (!isPaused) {
若是處於非暫停狀態,開始執行請求
request.begin();
} else {
//若是處於暫停狀態,將請求添加到等待集合
pendingRequests.add(request);
}
}
複製代碼
這個方法定義在 RequestTracker 中,這個類主要負責Request的執行,暫停,取消等等關於圖片請求的操做。咱們着重看 request.begin()
,這句代碼意味着開始執行圖片請求的處理。Request是個接口, request.begin()
實際調用的是Request的子類 GenericRequest 的begin方法,咱們跟進去看一下:
@Override
public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//若是長寬尺寸已經肯定
onSizeReady(overrideWidth, overrideHeight);
} else {
//獲取長寬尺寸,獲取完以後會調用onSizeReady(overrideWidth, overrideHeight)
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(overrideWidth, overrideHeight)
流程;若是未肯定,先獲取長寬,再走 onSizeReady(overrideWidth, overrideHeight)
@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;
//核心代碼,加載圖片
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.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this)
, 咱們看一下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();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
//使用LruCache獲取緩存
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);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
複製代碼
load方法位於 Engine 類中。load方法內部會從三個來源獲取圖片數據,咱們最熟悉的就是LruCache了。如何獲取數據過於複雜,這裏就再也不展開分析,咱們這裏主要關注圖片數據獲取到以後的操做。獲取到圖片數據以後,經過 cb.onResourceReady(cached)
來處理,咱們來看一下這個回調的具體實現:
@Override
public void onResourceReady(Resource<?> resource) {
if (resource == null) {
onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
+ " inside, but instead got null."));
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
releaseResource(resource);
onException(new Exception("Expected to receive an object of " + transcodeClass
+ " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
+ " inside Resource{" + resource + "}."
+ (received != null ? "" : " "
+ "To indicate failure return a null Resource object, "
+ "rather than a Resource object containing null data.")
));
return;
}
if (!canSetResource()) {
releaseResource(resource);
// We can't set the status to complete before asking canSetResource(). status = Status.COMPLETE; return; } //核心是這一句 onResourceReady(resource, (R) received); } 複製代碼
咱們繼續看 onResourceReady(resource, (R) received) 這個方法:
private void onResourceReady(Resource<?> resource, R result) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
isFirstResource)) {
GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
//核心,經過調用target的onResourceReady方法加載圖片
target.onResourceReady(result, animation);
}
notifyLoadSuccess();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
+ (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
}
}
複製代碼
咱們能夠看到核心代碼:target.onResourceReady(result, animation)
,其實在這句代碼的內部最終是經過:
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
public DrawableImageViewTarget(ImageView view) {
super(view);
}
@Override
protected void setResource(Drawable resource) {
view.setImageDrawable(resource);
}
}
複製代碼
本質是經過 setResource(Drawable resource) 來實現的,在這個方法的內部調用了Android內部最經常使用的加載圖片的方法 view.setImageDrawable(resource) 。
到此爲止,into方法基本已經分析完了,咱們忽略了網絡圖片獲取的過程,專一於獲取圖片後的處理。如今來對into方法作個總結:
Glide的基本源碼分析其實到這裏已經結束了,但提起圖片加載,LruCache是一個不可忽視的關鍵點,在Glide源碼分析的最後咱們再來分析一下LruCache的源碼,這個LruCache來自於 android.support.v4.util 中:
public class LruCache<K, V> {
//存儲緩存
private final LinkedHashMap<K, V> map;
//當前緩存的總大小
private int size;
//最大緩存大小
private int maxSize;
//添加到緩存的個數
private int putCount;
//建立的個數
private int createCount;
//移除的個數
private int evictionCount;
//命中個數
private int hitCount;
//未命中個數
private int missCount;
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
//從新設置最大緩存
public void resize(int maxSize) {
//確保最大緩存大於0
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
//對當前的緩存作一些操做以適應新的最大緩存大小
trimToSize(maxSize);
}
//獲取緩存
public final V get(K key) {
//確保key不爲null
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
//若是能夠獲取key對應的value
if (mapValue != null) {
//命中數加一
hitCount++;
return mapValue;
}
//若是根據key獲取的value爲null,未命中數加一
missCount++;
}
//省略無關代碼......
}
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
//添加到緩存的個數加一
putCount++;
//更新當前緩存大小
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
//若是以前map中對應key存在value不爲null,因爲重複的key新添加的value會覆蓋上一個value,因此當前緩存大小應該再減去以前value的大小
size -= safeSizeOf(key, previous);
}
}
//根據緩存最大值調整緩存
trimToSize(maxSize);
return previous;
}
//根據最大緩存大小對map中的緩存作調整
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
//當前緩存大小小於最大緩存,或LinkedHashMap爲空時跳出循環
if (size <= maxSize || map.isEmpty()) {
break;
}
//遍歷LinkedHashMap,刪除頂部的(也就是最早添加的)元素,直到當前緩存大小小於最大緩存,或LinkedHashMap爲空
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
//省略無關代碼......
}
}
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
return previous;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
protected int sizeOf(K key, V value) {
return 1;
}
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
@Override public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format(Locale.US, "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
maxSize, hitCount, missCount, hitPercent);
}
}
複製代碼
其實從上面的代碼能夠看出,LruCache內部主要靠一個LinkedHashMap來存儲緩存,這裏使用LinkedHashMap而不使用普通的HashMap正是看中了它的順序性,即LinkedHashMap中元素的存儲順序就是咱們存入的順序,而HashMap則沒法保證這一點。
咱們都知道Lru算法就是最近最少使用的算法,而LruCache是如何保證在緩存大於最大緩存大小以後移除的就是最近最少使用的元素呢?關鍵在於 trimToSize(int maxSize) 這個方法內部,在它的內部開啓了一個循環,遍歷LinkedHashMap,刪除頂部的(也就是最早添加的)元素,直到當前緩存大小小於最大緩存,或LinkedHashMap爲空。這裏須要注意的是因爲LinkedHashMap的特色,它的存儲順序就是存放的順序,因此位於頂部的元素就是最近最少使用的元素,正是因爲這個特色,從而實現了當緩存不足時優先刪除最近最少使用的元素。