在早期的Android开发中,图片加载其实一直是个比较麻烦的问题。咱们在处理图片时会遇到各类各样的问题:内存溢出、列表中图片错位等等。但到了现在,这些问题基本上是不会再遇到了。因为不少的优秀的图片加载框架帮咱们处理了图片相关问题的痛点,因此如今Android中关于图片加载的部分变得很是简单。Android中最著名的图片加载框架就是Glide了,咱们今天来深刻研究一下Glide的源码。android
以Glide3.8.0版原本分析,咱们先看下最多见使用方法:算法
Glide.with(fragment) .load(myUrl) .into(imageView);
上面的代码是咱们很是熟悉的Glide的基本用法,分为3个步骤:缓存
在了解到Glide的3个入口方法以后,我会按照这3个方法来进行源码的分析网络
看一下with(context)d的源码:app
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 :ide
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时的操做:fetch
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时的操做:ui
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) ,咱们来看一下:
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 方法已经基本介绍完毕了,作一下总结:
经过retriever.get(context)获取RequestManager,在get(context)方法中经过对context类型的判断作不一样的处理:
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<T> 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; }
咱们梳理一下方法中的逻辑:
逻辑仍是比较清晰的,这里有一个问题须要说明一下。为何要终止并清除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)
能够明白,主要的逻辑仍是在 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; //核心代码,加载图片 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的特色,它的存储顺序就是存放的顺序,因此位于顶部的元素就是最近最少使用的元素,正是因为这个特色,从而实现了当缓存不足时优先删除最近最少使用的元素。