原文连接: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的缓存设计,提出几点小建议: