參考
作者:BlackFlag
鏈接:[http://www.reibang.com/p/57123450a9c8]
基本概念
使用
implementation 'com.github.bumptech.glide:glide:3.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:3.8.0'
Glide.with(this).load(url).into(imageView);
Glide.with(getApplicationContext()) //指定Context
.load(url) //指定圖片的URL
.placeholder(R.mipmap.ic_launcher) //指定圖片未成功加載前顯示的圖片
.error(R.mipmap.ic_launcher) //指定圖片加載失敗顯示的圖片
.override(300, 300) //指定圖片的尺寸
.fitCenter() //指定圖片縮放類型為
.centerCrop() //指定圖片縮放類型為
.skipMemoryCache(true) //跳過內(nèi)存緩存
.crossFade(1000) //設(shè)置漸變式顯示的時間
.diskCacheStrategy(DiskCacheStrategy.NONE) //跳過磁盤緩存
.diskCacheStrategy(DiskCacheStrategy.SOURCE) //僅僅只緩存原理的全分辨率的圖像
.diskCacheStrategy(DiskCacheStrategy.RESULT) //僅僅緩存最終的圖像
.diskCacheStrategy(DiskCacheStrategy.ALL) //緩存所有版本的圖像
.priority(Priority.HIGH) //指定優(yōu)先級.Glide將會為他們作為一個準(zhǔn)則突那,并盡可能的處理這些請求盏混,但是并不能保證100%實施
.into(imageView); //指定顯示圖片的ImageView
1肥矢、with(*)方法
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);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
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);
}
1帐我、獲取RequestManagerRetriever.get()得到retriever
2、通過retriever.get(context)獲得RequestManager對象并返回
3菠净、with通過傳入不同的參數(shù)禁舷,Context彪杉、Activity、Fragment等等牵咙,主要是通過相應(yīng)的生命周期是管理加載圖片派近,避免了消耗多余的資源,也避免了在Activity銷毀之后加載圖片從而導(dǎo)致的空指針問題洁桌。
2渴丸、RequestManagerRetriever
上面這個方法主要是通過傳入context的不同類型來做不同的操作。context可以是Application战坤、FragmentActivity曙强、Activity或者是ContextWrapper。getApplicationManager(Context context)通過單例模式創(chuàng)建并返回了 applicationManager
如果不是主線程或者版本過低途茫,還是通過get(Application)方法碟嘴,否則通過fragmentGet(activity, fm)方法。
fragmentGet 在當(dāng)前的activity創(chuàng)建沒有界面的fragment并add進(jìn)activity囊卜,并將這個fragment與RequestManager進(jìn)行綁定娜扇,實現(xiàn)對activity生命周期的監(jiān)聽
總結(jié)
- 通過RequestManagerRetriever的get獲取RequestManagerRetriever對象
- 通過retriever.get(context)獲取RequestManager,在get(context)方法中通過對context類型的判斷做不同的處理:
- context是Application栅组,通過getApplicationManager(Context context) 創(chuàng)建并返回一個RequestManager對象
- context是Activity雀瓢,通過fragmentGet(activity, fm)在當(dāng)前activity創(chuàng)建并添加一個沒有界面的fragment,從而實現(xiàn)圖片加載與activity的生命周期相綁定玉掸,之后創(chuàng)建并返回一個RequestManager對象
3刃麸、load(url)
通過RequestManager調(diào)用load方法——>DrawableTypeRequest<String>) fromString().load(string)
先看fromString方法
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
創(chuàng)建并返回了一個DrawableTypeRequest。
接下來看load方法
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}
返回DrawableTypeRequest<String>對象司浪。跟進(jìn)父類的load看看
* @param model The model to load data for, or null.
* @return This request builder.
*/
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
this.model = model;
isModelSet = true;
return this;
}
DrawableRequestBuilder是GenericRequestBuilder子類泊业,前者是Drawable請求的構(gòu)建者,后者是通用的請求構(gòu)建者啊易。這個load方法其實是把我們傳入的String類型的URL存入了內(nèi)部的model成員變量中吁伺,再將數(shù)據(jù)來源是否已經(jīng)設(shè)置的標(biāo)志位 isModelSet 設(shè)置為true,意味著我們在調(diào)用 Glide.with(context).load(url)
之后數(shù)據(jù)來源已經(jīng)設(shè)置成功了租谈。
Glide.with(getApplicationContext()) //指定Context
.load(url) //指定圖片的URL
.placeholder(R.mipmap.ic_launcher) //指定圖片未成功加載前顯示的圖片
.error(R.mipmap.ic_launcher) //指定圖片加載失敗顯示的圖片
.override(300, 300) //指定圖片的尺寸
.fitCenter() //指定圖片縮放類型為
.centerCrop() //指定圖片縮放類型為
.skipMemoryCache(true) //跳過內(nèi)存緩存
.crossFade(1000) //設(shè)置漸變式顯示的時間
.diskCacheStrategy(DiskCacheStrategy.NONE) //跳過磁盤緩存
.diskCacheStrategy(DiskCacheStrategy.SOURCE) //僅僅只緩存原理的全分辨率的圖像
.diskCacheStrategy(DiskCacheStrategy.RESULT) //僅僅緩存最終的圖像
.diskCacheStrategy(DiskCacheStrategy.ALL) //緩存所有版本的圖像
.priority(Priority.HIGH) //指定優(yōu)先級.Glide將會為他們作為一個準(zhǔn)則篮奄,并盡可能的處理這些請求,但是并不能保證100%實施
.into(imageView); //指定顯示圖片的ImageView
4割去、placeholder
DrawableRequestBuilder.class
/**
* {@inheritDoc}
*/
@Override
public DrawableRequestBuilder<ModelType> placeholder(int resourceId) {
super.placeholder(resourceId);
return this;
}
GenericRequestBuilder.class
/**
* Sets an Android resource id for a {@link android.graphics.drawable.Drawable} resource to display while a resource
* is loading.
*
* @param resourceId The id of the resource to use as a placeholder
* @return This request builder.
*/
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
int resourceId) {
this.placeholderId = resourceId;
return this;
}
GenericRequestBuilder通過builder模式為成員屬性進(jìn)行賦值
5窟却、into(imageView)方法
into之前的步驟其實就是創(chuàng)建了一個Request(加載圖片的配置請求)。info方法便是真正的執(zhí)行劫拗。
DrawableRequestBuilder.class
@Override
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
GenericRequestBuilder.class
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
* any resources Glide may have previously loaded into the view so they may be reused.
*
* @see Glide#clear(android.view.View)
*
* @param view The view to cancel previous loads for and load the new resource into.
* @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link 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()) {
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));
}
最終還是走到了GenericRequestBuilder.class中的into方法中间校。重點看
return into(glide.buildImageViewTarget(view, transcodeClass));
返回了Target<TranscodeType>對象。
Glide.class
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
/**
* A factory responsible for producing the correct type of {@link com.bumptech.glide.request.target.Target} for a given
* {@link android.view.View} subclass.
*/
public class ImageViewTargetFactory {
@SuppressWarnings("unchecked")
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (GlideDrawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new GlideDrawableImageViewTarget(view);
} else if (Bitmap.class.equals(clazz)) {
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException("Unhandled class: " + clazz
+ ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
通過對圖片來源類型的判斷页慷,創(chuàng)建并返回與圖片來源對應(yīng)的imageViewTarget憔足。
GenericRequestBuilder.class
/**
* Set the target the resource will be loaded into.
*
* @see Glide#clear(com.bumptech.glide.request.target.Target)
*
* @param target The target to load the resource into.
* @return The given 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");
}
//確保數(shù)據(jù)來源已經(jīng)確定,即已經(jīng)調(diào)用了load(url)方法
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}
//獲取當(dāng)前target已經(jīng)綁定的Request對象
Request previous = target.getRequest();
//如果當(dāng)前target已經(jīng)綁定了Request對象酒繁,則清空這個Request對象
if (previous != null) {
previous.clear();
//停止綁定到當(dāng)前target的上一個Request的圖片請求處理
requestTracker.removeRequest(previous);
previous.recycle();
}
//創(chuàng)建Request對象
Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
//執(zhí)行request
requestTracker.runRequest(request);
return target;
}
我們梳理一下方法中的邏輯:
- 獲取當(dāng)前target中的Request對象滓彰,如果存在,則清空并終止這個Request對象的執(zhí)行
- 創(chuàng)建新的Request對象并與當(dāng)前target綁定
- 執(zhí)行新創(chuàng)建的圖片處理請求Request
在沒有Glide之前州袒,我們處理ListView中的圖片加載其實是一件比較麻煩的事情揭绑。由于ListView中Item的復(fù)用機制,會導(dǎo)致網(wǎng)絡(luò)圖片加載的錯位或者閃爍郎哭。那我們解決這個問題的辦法也很簡單他匪,就是給當(dāng)前的ImageView設(shè)置tag,這個tag可以是圖片的URL等等夸研。當(dāng)從網(wǎng)絡(luò)中獲取到圖片時判斷這個ImageVIew中的tag是否是這個圖片的URL邦蜜,如果是就加載圖片,如果不是則跳過亥至。
在有了Glide之后悼沈,我們處理ListView或者Recyclerview中的圖片加載就很無腦了,根本不需要作任何多余的操作姐扮,直接正常使用就行了絮供。這其中的原理是Glide給我們處理了這些判斷,我們來看一下Glide內(nèi)部是如何處理的:
@Override
public void setRequest(Request request) {
setTag(request);
}
@Override
public Request getRequest() {
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;
}
private void setTag(Object tag) {
if (tagId == null) {
isTagUsedAtLeastOnce = true;
view.setTag(tag);
} else {
view.setTag(tagId, tag);
}
}
private Object getTag() {
if (tagId == null) {
return view.getTag();
} else {
return view.getTag(tagId);
}
}
target.getRequest()
和 target.setRequest(Request request)
本質(zhì)上還是通過setTag和getTag來做的處理茶敏,這也印證了我們上面所說壤靶。
繼續(xù)回到into方法中,在創(chuàng)建并綁定了Request后惊搏,關(guān)鍵的就是 requestTracker.runRequest(request)
來執(zhí)行我們創(chuàng)建的請求了贮乳。
/**
* Starts tracking the given request.
*/
public void runRequest(Request request) {
//將請求加入請求集合
requests.add(request);
if (!isPaused) {
//如果處于非暫停狀態(tài),開始執(zhí)行請求
request.begin();
} else {
//如果處于暫停狀態(tài)胀屿,將請求添加到等待集合
pendingRequests.add(request);
}
}
request.begin() 實際調(diào)用的是Request的子類 GenericRequest 的begin方法
/**
* {@inheritDoc}
*/
@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 {
target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
//開始加載圖片塘揣,先顯示占位圖
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
- 獲取圖片的長寬尺寸,如果長寬已經(jīng)確定宿崭,走
onSizeReady(overrideWidth, overrideHeight)
流程亲铡;如果未確定,先獲取長寬葡兑,再走onSizeReady(overrideWidth, overrideHeight)
- 圖片開始加載奖蔓,首先顯示占位圖
可以明白,主要的邏輯還是在 onSizeReady 這個方法中
/**
* A callback method that should never be invoked directly.
*/
@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));
}
}
重點看loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this);
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;
}
//開啟線程從網(wǎng)絡(luò)中加載圖片......
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方法內(nèi)部會從三個來源獲取圖片數(shù)據(jù)吆鹤,我們最熟悉的就是LruCache了。如何獲取數(shù)據(jù)過于復(fù)雜洲守,這里就不再展開分析疑务,我們這里主要關(guān)注圖片數(shù)據(jù)獲取到之后的操作沾凄。獲取到圖片數(shù)據(jù)之后,通過 cb.onResourceReady(cached)
來處理知允,我們來看一下這個回調(diào)的具體實現(xiàn):
/**
* A callback method that should never be invoked directly.
*/
@SuppressWarnings("unchecked")
@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);
}
跟進(jìn)onResourceReady(resource, (R) received);
/**
* Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
*
* @param resource original {@link Resource}, never <code>null</code>
* @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
*/
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(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);
最終回調(diào)到
/**
* A target for display {@link Drawable} objects in {@link ImageView}s.
*/
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
public DrawableImageViewTarget(ImageView view) {
super(view);
}
@Override
protected void setResource(Drawable resource) {
view.setImageDrawable(resource);
}
}
本質(zhì)是通過 setResource(Drawable resource) 來實現(xiàn)的绘闷,在這個方法的內(nèi)部調(diào)用了Android內(nèi)部最常用的加載圖片的方法 view.setImageDrawable(resource) 忽肛。
到此為止嚼隘,into方法基本已經(jīng)分析完了秉版,我們忽略了網(wǎng)絡(luò)圖片獲取的過程,專注于獲取圖片后的處理〉拥妫現(xiàn)在來對into方法做個總結(jié):
- 將imageview包裝成imageViewTarget
- 清除這個imageViewTarget之前綁定的請求姑尺,綁定新的請求
- 執(zhí)行新的請求
- 獲取圖片數(shù)據(jù)之后,成功則會調(diào)用ImageViewTarget中的onResourceReady()方法蝠猬,失敗則會調(diào)用ImageViewTarget中的onLoadFailed();二者的本質(zhì)都是通過調(diào)用Android中的imageView.setImageDrawable(drawable)來實現(xiàn)對imageView的圖片加載
6切蟋、LruCache源碼分析
在Glide源碼分析的最后我們再來分析一下LruCache的源碼,這個LruCache來自于 android.support.v4.util 中:
public class LruCache<K, V> {
//存儲緩存容器
private final LinkedHashMap<K, V> map;
/** Size of this cache in units. Not necessarily the number of elements. */
//當(dāng)前緩存的總大小
private int size;
//最大緩存大小
private int maxSize;
//添加到緩存的個數(shù)
private int putCount;
//創(chuàng)建的個數(shù)
private int createCount;
//移除的個數(shù)
private int evictionCount;
//命中個數(shù)
private int hitCount;
//未命中個數(shù)
private int missCount;
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
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);
}
/**
* Sets the size of the cache.
* @param maxSize The new maximum size.
*
* @hide
*/
//重新設(shè)置最大緩存
public void resize(int maxSize) {
//確保最大緩存大于0
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
//對當(dāng)前的緩存做一些操作以適應(yīng)新的最大緩存大小
trimToSize(maxSize);
}
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
//獲取緩存
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//如果可以獲取key對應(yīng)的value
mapValue = map.get(key);
if (mapValue != null) {
//命中數(shù)加一
hitCount++;
return mapValue;
}
//如果根據(jù)key獲取的value為null吱雏,未命中數(shù)加一
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
//添加到緩存的個數(shù)加一
putCount++;
//更新當(dāng)前緩存大小
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
//如果之前map中對應(yīng)key存在value不為null敦姻,由于重復(fù)的key新添加的value會覆蓋上一個value,所以當(dāng)前緩存大小應(yīng)該再減去之前value的大小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//根據(jù)緩存最大值調(diào)整緩存
trimToSize(maxSize);
return previous;
}
/**
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
//根據(jù)最大緩存大小對map中的緩存做調(diào)整
private 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!");
}
//當(dāng)前緩存大小小于最大緩存歧杏,或LinkedHashMap為空時跳出循環(huán)
if (size <= maxSize) {
break;
}
// BEGIN LAYOUTLIB CHANGE
// get the last item in the linked list.
// This is not efficient, the goal here is to minimize the changes
// compared to the platform version.
Map.Entry<K, V> toEvict = null;
//遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素镰惦,直到當(dāng)前緩存大小小于最大緩存,或LinkedHashMap為空
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
/**
* Removes the entry for {@code key} if it exists.
*
* @return the previous value mapped by {@code key}.
*/
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);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
/**
* Called for entries that have been evicted or removed. This method is
* invoked when a value is evicted to make space, removed by a call to
* {@link #remove}, or replaced by a call to {@link #put}. The default
* implementation does nothing.
*
* <p>The method is called without synchronization: other threads may
* access the cache while this method is executing.
*
* @param evicted true if the entry is being removed to make space, false
* if the removal was caused by a {@link #put} or {@link #remove}.
* @param newValue the new value for {@code key}, if it exists. If non-null,
* this removal was caused by a {@link #put}. Otherwise it was caused by
* an eviction or a {@link #remove}.
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
/**
* Called after a cache miss to compute a value for the corresponding key.
* Returns the computed value or null if no value can be computed. The
* default implementation returns null.
*
* <p>The method is called without synchronization: other threads may
* access the cache while this method is executing.
*
* <p>If a value for {@code key} exists in the cache when this method
* returns, the created value will be released with {@link #entryRemoved}
* and discarded. This can occur when multiple threads request the same key
* at the same time (causing multiple values to be created), or when one
* thread calls {@link #put} while another is creating a value for the same
* key.
*/
protected V create(K key) {
return null;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size
* is the number of entries and max size is the maximum number of entries.
*
* <p>An entry's size must not change while it is in the cache.
*/
protected int sizeOf(K key, V value) {
return 1;
}
/**
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* For caches that do not override {@link #sizeOf}, this returns the number
* of entries in the cache. For all other caches, this returns the sum of
* the sizes of the entries in this cache.
*/
public synchronized final int size() {
return size;
}
/**
* For caches that do not override {@link #sizeOf}, this returns the maximum
* number of entries in the cache. For all other caches, this returns the
* maximum sum of the sizes of the entries in this cache.
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* Returns the number of times {@link #get} returned a value that was
* already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* Returns the number of times {@link #get} returned null or required a new
* value to be created.
*/
public synchronized final int missCount() {
return missCount;
}
/**
* Returns the number of times {@link #create(Object)} returned a value.
*/
public synchronized final int createCount() {
return createCount;
}
/**
* Returns the number of times {@link #put} was called.
*/
public synchronized final int putCount() {
return putCount;
}
/**
* Returns the number of values that have been evicted.
*/
public synchronized final int evictionCount() {
return evictionCount;
}
/**
* Returns a copy of the current contents of the cache, ordered from least
* recently accessed to most recently accessed.
*/
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
maxSize, hitCount, missCount, hitPercent);
}
}
LruCache(最近最少使用算法)內(nèi)部主要靠一個LinkedHashMap來存儲緩存犬绒,這里使用LinkedHashMap而不使用普通的HashMap正是看中了它的順序性旺入,即LinkedHashMap中元素的存儲順序就是我們存入的順序,而HashMap則無法保證這一點凯力。關(guān)鍵在于 trimToSize(int maxSize) 這個方法內(nèi)部茵瘾,在它的內(nèi)部開啟了一個循環(huán),遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素咐鹤,直到當(dāng)前緩存大小小于最大緩存拗秘,或LinkedHashMap為空。這里需要注意的是由于LinkedHashMap的特點祈惶,它的存儲順序就是存放的順序雕旨,所以位于頂部的元素就是最近最少使用的元素,正是由于這個特點捧请,從而實現(xiàn)了當(dāng)緩存不足時優(yōu)先刪除最近最少使用的元素凡涩。