目录
- with
- load
- into
- 原理总结
- 缓存
- LruCache
首先引入依赖
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
下面一行代码,就是Glide最简单的使用方式了
Glide.with(this).load(url).into(imageView)
with
首先,我们来看with,其实with的功能就是根据传入的context来获取图片请求管理器RequestManager,用来启动和管理图片请求。
public static RequestManager with(@NonNull FragmentActivity activity) { | |
return getRetriever(activity).get(activity); | |
} |
context可以传入app,activity和fragment,这关系着图片请求的生命周期。通常使用当前页面的context,这样当我们打开一个页面加载图片,然后退出页面时,图片请求会跟随页面销毁而被取消,而不是继续加载浪费资源。
当context是app时,获得的RequestManager是一个全局单例,图片请求的生命周期会跟随整个app。
注意:如果with发生在子线程,不管context是谁,都返回应用级别的RequestManager单例。
private RequestManager getApplicationManager( { Context context) | |
// Either an application context or we're on a background thread. | |
if (applicationManager == null) { | |
synchronized (this) { | |
if (applicationManager == null) { | |
// Normally pause/resume is taken care of by the fragment we add to the fragment or | |
// activity. However, in this case since the manager attached to the application will not | |
// receive lifecycle events, we must force the manager to start resumed using | |
// ApplicationLifecycle. | |
// TODO(b/): Factor out this Glide.get() call. | |
Glide glide = Glide.get(context.getApplicationContext()); | |
applicationManager = | |
factory.build( | |
glide, | |
new ApplicationLifecycle(), | |
new EmptyRequestManagerTreeNode(), | |
context.getApplicationContext()); | |
} | |
} | |
} | |
return applicationManager; | |
} |
当context是Activity时,会创建一个无界面的fragment添加到Activity,用于感知Activity的生命周期,同时创建RequestManager给该fragment持有。
private RequestManager supportFragmentGet( | |
Context context, | |
FragmentManager fm, | |
Fragment parentHint, | |
boolean isParentVisible) { | |
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint); | |
RequestManager requestManager = current.getRequestManager(); | |
if (requestManager == null) { | |
// TODO(b/): Factor out this Glide.get() call. | |
Glide glide = Glide.get(context); | |
requestManager = | |
factory.build( | |
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); | |
// This is a bit of hack, we're going to start the RequestManager, but not the | |
// corresponding Lifecycle. It's safe to start the RequestManager, but starting the | |
// Lifecycle might trigger memory leaks. See b/ | |
if (isParentVisible) { | |
requestManager.onStart(); | |
} | |
current.setRequestManager(requestManager); | |
} | |
return requestManager; | |
} |
load
load方法会得到一个图片请求构建器RequestBuilder,用来创建图片请求。
public RequestBuilder<Drawable> load(String string) { | |
return asDrawable().load(string); | |
} |
into
首先是根据ImageView的ScaleType,来配置参数
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { | |
Util.assertMainThread(); | |
Preconditions.checkNotNull(view); | |
BaseRequestOptions | > requestOptions = this;|
if (!requestOptions.isTransformationSet() | |
&& requestOptions.isTransformationAllowed() | |
&& view.getScaleType() != null) { | |
// Clone in this method so that if we use this RequestBuilder to load into a View and then | |
// into a different target, we don't retain the transformation applied based on the previous | |
// View's scale type. | |
switch (view.getScaleType()) { | |
case CENTER_CROP: | |
requestOptions = requestOptions.clone().optionalCenterCrop(); | |
break; | |
case CENTER_INSIDE: | |
requestOptions = requestOptions.clone().optionalCenterInside(); | |
break; | |
case FIT_CENTER: | |
case FIT_START: | |
case FIT_END: | |
requestOptions = requestOptions.clone().optionalFitCenter(); | |
break; | |
case FIT_XY: | |
requestOptions = requestOptions.clone().optionalCenterInside(); | |
break; | |
case CENTER: | |
case MATRIX: | |
default: | |
// Do nothing. | |
} | |
} | |
return into( | |
glideContext.buildImageViewTarget(view, transcodeClass), | |
/*targetListener=*/ null, | |
requestOptions, | |
Executors.mainThreadExecutor()); | |
} |
继续跟进into,会创建图片请求,获取Target载体已有的请求,对比两个请求,如果等效,启动异步请求。然后,图片载体绑定图片请求,也就是imageView setTag为request
private <Y extends Target<TranscodeType>> Y into( | |
Y target, | |
RequestListener<TranscodeType> targetListener, | |
BaseRequestOptions<?> options, | |
Executor callbackExecutor) { | |
Preconditions.checkNotNull(target); | |
if (!isModelSet) { | |
throw new IllegalArgumentException("You must call #load() before calling #into()"); | |
} | |
Request request = buildRequest(target, targetListener, options, callbackExecutor); | |
Request previous = target.getRequest(); | |
if (request.isEquivalentTo(previous) | |
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { | |
// If the request is completed, beginning again will ensure the result is re-delivered, | |
// triggering RequestListeners and Targets. If the request is failed, beginning again will | |
// restart the request, giving it another chance to complete. If the request is already | |
// running, we can let it continue running without interruption. | |
if (!Preconditions.checkNotNull(previous).isRunning()) { | |
// Use the previous request rather than the new one to allow for optimizations like skipping | |
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions | |
// that are done in the individual Request. | |
previous.begin(); | |
} | |
return target; | |
} | |
requestManager.clear(target); | |
target.setRequest(request); | |
requestManager.track(target, request); | |
return target; | |
} |
继续跟进异步请求 requestManager.track(target, request)
synchronized void track(@NonNull Target<?> target, @NonNull Request request) { | |
targetTracker.track(target); | |
requestTracker.runRequest(request); | |
} | |
public void runRequest(@NonNull Request request) { | |
requests.add(request); | |
if (!isPaused) { | |
request.begin();//开启图片请求 | |
} else { | |
request.clear(); | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Paused, delaying request"); | |
} | |
pendingRequests.add(request);//如果是暂停状态,就把请求存起来 | |
} | |
} |
到这里就启动了图片请求了,我们继续跟进request.begin()
public void begin() { | |
synchronized (requestLock) { | |
//...... | |
if (Util.isValidDimensions(overrideWidth, overrideHeight)) { | |
//如果有尺寸,开始加载 | |
onSizeReady(overrideWidth, overrideHeight); | |
} else { | |
//如果无尺寸就先去获取 | |
target.getSize(this); | |
} | |
//...... | |
} | |
} |
然后继续瞧瞧onSizeReady
public void onSizeReady(int width, int height) { | |
stateVerifier.throwIfRecycled(); | |
synchronized (requestLock) { | |
//...... | |
loadStatus = | |
engine.load( | |
glideContext, | |
model, | |
requestOptions.getSignature(), | |
this.width, | |
this.height, | |
requestOptions.getResourceClass(), | |
transcodeClass, | |
priority, | |
requestOptions.getDiskCacheStrategy(), | |
requestOptions.getTransformations(), | |
requestOptions.isTransformationRequired(), | |
requestOptions.isScaleOnlyOrNoTransform(), | |
requestOptions.getOptions(), | |
requestOptions.isMemoryCacheable(), | |
requestOptions.getUseUnlimitedSourceGeneratorsPool(), | |
requestOptions.getUseAnimationPool(), | |
requestOptions.getOnlyRetrieveFromCache(), | |
this, | |
callbackExecutor); | |
//...... | |
} | |
} |
跟进engine.load
public <R> LoadStatus load( | |
GlideContext glideContext, | |
Object model, | |
Key signature, | |
int width, | |
int height, | |
Class<?> resourceClass, | |
Class<R> transcodeClass, | |
Priority priority, | |
DiskCacheStrategy diskCacheStrategy, | |
Map<Class<?>, Transformation<?>> transformations, | |
boolean isTransformationRequired, | |
boolean isScaleOnlyOrNoTransform, | |
Options options, | |
boolean isMemoryCacheable, | |
boolean useUnlimitedSourceExecutorPool, | |
boolean useAnimationPool, | |
boolean onlyRetrieveFromCache, | |
ResourceCallback cb, | |
Executor callbackExecutor) { | |
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() :; | |
EngineKey key = | |
keyFactory.buildKey( | |
model, | |
signature, | |
width, | |
height, | |
transformations, | |
resourceClass, | |
transcodeClass, | |
options); | |
EngineResource<?> memoryResource; | |
synchronized (this) { | |
//从内存加载 | |
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); | |
if (memoryResource == null) { //如果内存里没有 | |
return waitForExistingOrStartNewJob( | |
glideContext, | |
model, | |
signature, | |
width, | |
height, | |
resourceClass, | |
transcodeClass, | |
priority, | |
diskCacheStrategy, | |
transformations, | |
isTransformationRequired, | |
isScaleOnlyOrNoTransform, | |
options, | |
isMemoryCacheable, | |
useUnlimitedSourceExecutorPool, | |
useAnimationPool, | |
onlyRetrieveFromCache, | |
cb, | |
callbackExecutor, | |
key, | |
startTime); | |
} | |
} | |
cb.onResourceReady( | |
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false); | |
return null; | |
} | |
private <R> LoadStatus waitForExistingOrStartNewJob( | |
GlideContext glideContext, | |
Object model, | |
Key signature, | |
int width, | |
int height, | |
Class<?> resourceClass, | |
Class<R> transcodeClass, | |
Priority priority, | |
DiskCacheStrategy diskCacheStrategy, | |
Map<Class<?>, Transformation<?>> transformations, | |
boolean isTransformationRequired, | |
boolean isScaleOnlyOrNoTransform, | |
Options options, | |
boolean isMemoryCacheable, | |
boolean useUnlimitedSourceExecutorPool, | |
boolean useAnimationPool, | |
boolean onlyRetrieveFromCache, | |
ResourceCallback cb, | |
Executor callbackExecutor, | |
EngineKey key, | |
long startTime) { | |
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); | |
if (current != null) { | |
current.addCallback(cb, callbackExecutor); | |
if (VERBOSE_IS_LOGGABLE) { | |
logWithTimeAndKey("Added to existing load", startTime, key); | |
} | |
return new LoadStatus(cb, current); | |
} | |
EngineJob<R> engineJob = | |
engineJobFactory.build( | |
key, | |
isMemoryCacheable, | |
useUnlimitedSourceExecutorPool, | |
useAnimationPool, | |
onlyRetrieveFromCache); | |
DecodeJob<R> decodeJob = | |
decodeJobFactory.build( | |
glideContext, | |
model, | |
key, | |
signature, | |
width, | |
height, | |
resourceClass, | |
transcodeClass, | |
priority, | |
diskCacheStrategy, | |
transformations, | |
isTransformationRequired, | |
isScaleOnlyOrNoTransform, | |
onlyRetrieveFromCache, | |
options, | |
engineJob); | |
jobs.put(key, engineJob); | |
engineJob.addCallback(cb, callbackExecutor); | |
engineJob.start(decodeJob); | |
if (VERBOSE_IS_LOGGABLE) { | |
logWithTimeAndKey("Started new load", startTime, key); | |
} | |
return new LoadStatus(cb, engineJob); | |
} |
DecodeJob是一个Runnable,它通过一系列的调用,会来到HttpUrlFetcher的loadData方法
public void loadData( | |
super InputStream> callback) { | Priority priority, DataCallback<?|
long startTime = LogTime.getLogTime(); | |
try { | |
//获取输入流,此处使用的是HttpURLConnection | |
InputStream result = loadDataWithRedirects(glideUrl.toURL(),, null, glideUrl.getHeaders()); | |
//回调出去 | |
callback.onDataReady(result); | |
} catch (IOException e) { | |
if (Log.isLoggable(TAG, Log.DEBUG)) { | |
Log.d(TAG, "Failed to load data for url", e); | |
} | |
callback.onLoadFailed(e); | |
} finally { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)); | |
} | |
} | |
} |
至此,网络请求结束,最后把图片设置上去就行了,在SingleRequest的onResourceReady方法,它会把结果回调给Target载体
target.onResourceReady(result, animation);
继续跟进它,最终会执行setResource,把图片设置上去
protected void setResource(@Nullable Drawable resource) { | |
view.setImageDrawable(resource); | |
} |
原理总结
with根据传入的context来获取图片请求管理器RequestManager,当传入的context是App时,获得的RequestManager是一个全局单例,图片请求的生命周期会跟随这个应用,当传入的是Activity时,会创建一个无界面的空fragment添加到Activity,用来感知Activity的生命周期。load会得到了一个图片请求构建器RequestBuilder,用来创建图片请求。into开启加载,先会根据ImageView的ScaleType来配置参数,创建图片请求,图片载体绑定图片请求,然后开启图片请求,先从内存中加载,如果内存里没有,会创建一个Runnable,通过一系列的调用,使用HttpURLConnection获取网络输入流,把结果回调出去,最后把回调结果设置上去就OK了。
缓存
Glide三级缓存原理:读取一张图片时,顺序是: 弱引用缓存,LruCache,磁盘缓存。
用Glide加载某张图片时,先去弱引用缓存中寻找图片,如果有则直接取出来使用,如果没有,则去LruCache中寻找,如果LruCache中有,则从中取出图片使用,并将它放入弱引用缓存中,如果都没有图片,则从磁盘缓存或网络中加载图片。
private EngineResource<?> loadFromMemory( | |
EngineKey key, boolean isMemoryCacheable, long startTime) { | |
if (!isMemoryCacheable) { | |
return null; | |
} | |
EngineResource<?> active = loadFromActiveResources(key); //从弱引用获取图片 | |
if (active != null) { | |
if (VERBOSE_IS_LOGGABLE) { | |
logWithTimeAndKey("Loaded resource from active resources", startTime, key); | |
} | |
return active; | |
} | |
EngineResource<?> cached = loadFromCache(key); //从LruCache获取缓存图片 | |
if (cached != null) { | |
if (VERBOSE_IS_LOGGABLE) { | |
logWithTimeAndKey("Loaded resource from cache", startTime, key); | |
} | |
return cached; | |
} | |
return null; | |
} |
不过,这也会产生一个问题,就是Glide加载图片时,URL不变但是图片变了,这种情况会还是以前的旧图片。因为Glide加载图片会将图片缓存到本地,如果url不变则直接读取缓存不会再网络加载。
解决方案:
清除缓存让后台每次都更改图片的名字图片地址选用 ”url?key="+随机数这种格式
LruCache
LruCache就是维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。其内部维护了一个集合LinkedHashMap,LinkHashMap继承HashMap,在HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问数据的链表指针,该LinkedHashMap是以访问顺序排序的,当调用put()方法时,就会在集合中添加元素,判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。
那么,问题来了,如果把一个(100 * 100)的图片放到(800 * 800)的Imageview中会怎么样呢?由上可知,Glide会为每个不同尺寸的Imageview缓存一张图片,也就是说不管这张图片有没有加载过,只要Imageview的尺寸不一样,Glide就会重新加载一次,这时候,它会在加载的Imageview之前从网络上重新下载,然后再缓存。举个例子,如果一个页面的Imageview是100 * 100,另一个页Imageview是800 * 800,它俩展示同一张图片的话,Glide会下载两次图片,并且缓存两张图片,因为Glide缓存Key的生成条件之一就是控件的长宽。
除了缓存,Glide还有一点我觉得做的非常好,就是在图片加载中关闭页面,此页面也不会造成内存泄漏,因为Glide在加载资源的时候,如果是在 Activity,Fragment 这类有生命周期的组件上进行的话,会创建一个无界面的Fragment加入到FragmentManager之中,感知生命周期,当 Activity,Fragment进入不可见,或者已经销毁的时候,Glide会停止加载资源。但是如果,是在非生命周期的组件上进行时,会采用Application的生命周期贯穿整个应用,此时只有在应用程序关闭的时候才会停止加载。