本文基于Glide版本:
com.github.bumptech.glide:glide:4.11.0
Glide最常用的一行代码如下,也概括了Glide的初始化、加载图片(本地、缓存、网络图片)、绑定显示的流程。本文就从该行代码开启Glide的源码之旅。
Glide.with(this).load(url).into(mainPic);
1-初始化with()
首先是Glide.with()方法,通过该方法主要是通过RequestManagerRetriever获取一个RequestManager对象。RequestManager是处理图片加载过程的具体实现类,后面详细讲述。
调用链路:(1)Glide.with
-->(2)Glide.getRetriever
-->(3)Glide.get
-->(4)Glide.getRequestManagerRetriever
-->(5)RequestManagerRetriever.get
-->(6)RequestManager
(1)Glide.with
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
(2)Glide.getRetriever获取RequestManagerRetriever
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
//判空检测,判断对象空指针以及Activity/Fragment是否处于活跃状态
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}
(3)Glide.get(context)获取Glide的全局单例。这么多逻辑主要是处理用户自定义AppGlideModule的情况。
- 继承AppGlideModule实现自定义Glide,重写网络加载框架、缓存路径、自定义缓存等定制内容,在class前加上@GlideModule注解
- 通过Annotation Processer在编译时根据@GlideModule注解来生成自定义Glide的代理类GeneratedAppGlideModuleImpl
- 通过反射获取代理类的构造方法并实例化返回
- 若APT方法获取失败则尝试从manifest文件中获取自定义GlideModule。manifest配置自定义module是Glide4.0以前的方式,新版本基本都采用注解APT的方式实现。
- 若都没有自定义的module,则通过GliderBuilder的工厂模式生成实例。
public static Glide get(@NonNull Context context) {
if (glide == null) {
//获取自定义AppGlideModule的代理类GeneratedAppGlideModuleImpl
GeneratedAppGlideModule annotationGeneratedModule =
getAnnotationGeneratedGlideModules(context.getApplicationContext());
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
(4)Glide.getRequestManagerRetriever获取Glide.get方法中初始化的RequestManagerRetriever对象。
public RequestManagerRetriever getRequestManagerRetriever() {
return requestManagerRetriever;
}
(5)RequestManagerRetriever.get方法根据调用的context及是否主线程返回对应的RequestManager。context为FragmentActivity时调用:
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
//Activity、FragmentActivity情况
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper
&& ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
return get(((ContextWrapper) context).getBaseContext());
}
}
//非UI线程情况
return getApplicationManager(context);
}
如果context是Fragment时调用:
public RequestManager get(@NonNull Fragment fragment) {
Preconditions.checkNotNull(
fragment.getContext(),
"You cannot start a load on a fragment before it is attached or after it is destroyed");
//非UI线程
if (Util.isOnBackgroundThread()) {
return get(fragment.getContext().getApplicationContext());
} else {
//context为Fragment情况
FragmentManager fm = fragment.getChildFragmentManager();
return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible());
}
}
总的来说分三种情况:
@1. 非UI线程
@2. UI线程Activity
@3. UI线程Fragment
@1.先来分析第一种情况,第一种情况较简单,通过getApplicationManager返回一个全局的RequestManager单例,该RequestManager是通过ApplicationLifecycle()构造的,也就是说只能感知application的生命周期,使用时需要注意。
@NonNull
private RequestManager getApplicationManager(@NonNull Context context) {
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
return applicationManager;
}
@2.再来看UI线程Activity情况
public RequestManager get(@NonNull FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
//断言activity未销毁
assertNotDestroyed(activity);
//获取FragmentManager
FragmentManager fm = activity.getSupportFragmentManager();
//核心!!通过fm向当前activity添加一个SupportRequestManagerFragment实例
//通过SupportRequestManagerFragment感知activity的生命周期
return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
向当前activity添加一个fragment,并通过持有该fragment的lifecycle实例来感知当前activity的生命周期状态。通过fragment感知生命周期这种套路很多地方都能看到例如LifeCycle。
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
@3. UI线程Fragment。第三种情况就不必多说了代码和Activty大部分都是复用的,只不过区别在于获取fm上,通过Fragment.getChildFragmentManager来获取fm,向Fragment添加一个子Fragment。子Fragment 则会通过 ChildFragmentManager 和 Fragment 保持生命周期一致。
这样做的好处是可以感知调用方的生命周期,避免生命周期不一致导致的内存泄漏或者空指针问题。
总结一下Glide.with()初始化过程:
- (1)初始化Glide并获取Glide实例
- 先通过APT方式产生的自定义GlideModule实例化
- 没有则通过manifest方式自定义GlideModule实例化
- 没有则用GliderBuilder构造默认实例
- (2)获取Glide的RequestManagerRetriever
- (3)根据是否UI线程及context类型返回对应的RequestManager
2-加载准备load()
RequestManager.load()函数并未实现图片的加载,而是构造了一个RequestBuilder实例
protected RequestBuilder(
@NonNull Glide glide,
RequestManager requestManager,
Class transcodeClass,
Context context) {
this.glide = glide;//glide实例
this.requestManager = requestManager;//RequestManager实例
this.transcodeClass = transcodeClass;//图片加载类型,默认Drawable
this.context = context;
this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
this.glideContext = glide.getGlideContext();
//@1.初始化监听
initRequestListeners(requestManager.getDefaultRequestListeners());
//@2.将设置的参数/默认参数赋值应用
apply(requestManager.getDefaultRequestOptions());
}
@1.初始化监听过程则是对于设置了图片加载监听的情况,RequestListener主要包含了两个回调:
- onLoadFaild加载失败
- onResourceReady资源获取成功
@2.将设置的参数/默认参数赋值应用。这里的参数是在调用时用户传入或者默认参数,通过RequestOptions来保存这些配置,包括图片的裁剪方式、兜底图、出场动画等配置
3-加载显示图片 into()
完成了with初始和load加载准备工作后,就可以开始获取图片并显示到目标ImageView上了。先从RequestBuilder.into开始,这里的RequestBuilder就是上一步load()生成的实例。
public ViewTarget into(@NonNull ImageView view) {
//断言是否UI线程,非UI线程抛出异常
Util.assertMainThread();
//判空
Preconditions.checkNotNull(view);
BaseRequestOptions> requestOptions = this;
//获取裁剪方式配置。注意这里使用的是clone的方式
//避免修改原始配置参数,导致相同RequestBuilder加载其他目标时配置被修改过。
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
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.
}
//@1.核心!!加载图片
return into(
//根据类型封装成对应的ViewTarget
//分为DrawableImageViewTarget、BitmapImageViewTarget
//分别调用ImageView的setImageDrawable、setImageBitmap来实现图片加载显示
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
@1.加载图片into
private > Y into(
@NonNull Y target,
@Nullable RequestListener 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();
// 这里做了请求优化处理,避免重复资源请求。同时满足如下两个条件时,直接复用前一个request。
//1.当前request和前一个request相同
//2.支持内存缓存cacheable=true或者前一个请求未成功完成isComplete=false
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
//如果前一个request处于非running状态,重新启动该请求
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
//如果是新请求,更新RequestTracker及TargetTracker
requestManager.clear(target);
target.setRequest(request);
//@2.发送加载请求
requestManager.track(target, request);
return target;
}
@2.发送加载请求。RequestManager.track方法
synchronized void track(@NonNull Target> target, @NonNull Request request) {
//存储ViewTarget
targetTracker.track(target);
//@3.将request加入请求集合并执行
requestTracker.runRequest(request);
}
这里涉及到的两个比较重要的类RequestTracker及TargetTracker。分别维护请求集合和加载目标集合
- TargetTracker比较好理解,主要维护了同一个context下所有ViewTarget的集合,并将context的lifecycle的生命周期变化同步给所有ViewTarget,持有的是ViewTarget的弱引用
- Set
> targets =
Collections.newSetFromMap(new WeakHashMap, Boolean>());
- Set
- RequestTracker是管理当前context下所有的图片加载请求任务Request。包含两个集合,requests保存待发起请求,pendingRequest存放中断状态下的请求。pendingRequest是用强引用List存储,便于恢复执行未完成的请求。
- Set
requests =
Collections.newSetFromMap(new WeakHashMap()); - List
pendingRequests = new ArrayList<>();
- Set
@3.将request加入请求集合并执行。RequestTracker.runRequest
public void runRequest(@NonNull Request request) {
//加入待执行请求集合requests
requests.add(request);
if (!isPaused) {
//@4.非中断状态,请求执行
request.begin();
} else {
//中断状态,请求状态清空并存入pendingRequests
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
isPaused状态依赖于context的lifecycle状态。若是ApplicationLifeCyle则app正常运行状态下isPaused=false;若是Fragment/Activity则在其LifeCycle对应的活跃状态下isPaused=false,非活跃状态isPaused=true。
@4.request.begin请求任务的执行,Request只是一个接口,这里具体实现类是SingleRequest。加载任务主要分两步,第一步测量加载尺寸,第二步进行加载
public void begin() {
synchronized (requestLock) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
。。。//省略
status = Status.WAITING_FOR_SIZE;
//@2.若指定了宽高,且尺寸有效测量完毕
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
//未指定宽高,则测量绑定ImageView的宽高,然后还是走到onSizeReady方法
//同时这里还会通过ViewTreeObserver监听ImageView的尺寸变化,
//尺寸变化时通知调整图片尺寸
target.getSize(this);
}
//@1.回调ViewTarget的onLoadStarted方法,并返回占位图
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
。。。
}
}
@1.ImageViewTarget.onLoadStarted方法中加载占位图,方法比较简单
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
@2.图片尺寸测量完毕后就开始加载图片了,下SingleRequest.onSizeReady方法中主要是通过调用Engine.load方法来加载图片。Engine即图片加载引擎。调用链SingleRequest.onSizeReady-->Engine.load
public LoadStatus load(。。。//入参省略) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
。。。//省略
EngineResource> memoryResource;
synchronized (this) {
//@3.从内存/硬盘缓存加载
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
//@4.缓存中没有,则通过引擎加载
return waitForExistingOrStartNewJob(。。。//入参省略);
}
}
//@5.回调ResourceCallback.onResourceReady方法,获取图片资源完成
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
无论是通过缓存@3还是通过网络下载@4,最终都会通过ResourceCallback.onResourceReady来完成图片获取回调,所以先从@5接着分析完整个流程链路。@3和@4涉及到Glide的核心-三级缓存机制,下节详细讲述。
@5.(1)回调ResourceCallback.onResourceReady
-->(2)SingleRequest.onResourceReady
- 回调对SingleRequest设置的RequestListener.onResourceReady
- 构建转场动画并回调taget.onResourceReady,这里的target即之前的DrawableImageViewTarget,回调方法在其父类.onResourceReady
-->(3)ImageViewTarget.onResourceReady
public void onResourceReady(@NonNull Z resource, @Nullable Transition super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
//调用DrawableImageViewTarget.setResource及maybeUpdateAnimatable
setResourceInternal(resource);
} else {
//若为AnimationDrawable等继承了Animatable接口的Drawable,执行其动画
maybeUpdateAnimatable(resource);
}
}
-->(4)DrawableImageViewTarget.setResource
-->(5)ImageView.setImageDrawable
至此整个Glide.with(this).load(url).into(mainPic);
流程就结束了,由于是限制在UI线程,也没有线程切换的操作,通过熟悉的ImageView.setImageDrawable将图片展示到界面。
RequestBuilder.inot加载图片过程总结:
- (1)RequestBuilder.into构建新的请求Request或启动上次相同Request,克隆图片裁剪方式配置到Request
- (2)RequestManager.track添加ViewTarget到TargetTracker,添加Request到RequestTracker并启动request
- (3)SingleRequest.begin开启请求
- 测量阶段,通过用户设置的宽高或获取目标ImageView宽高
- 测量完毕调用onResourceReady-->Engin.load加载图片
- (4)Engin.load通过Glide的三级缓存机制先从缓存中获取目标资源,否则通过网络请求资源
- (5)资源获取完毕调用ImageViewTarget.onResourceReady,在回调中设置Drawable到目标ImageView完成加载显示
4-Glide三级缓存机制
再回到第3节中的Engin.load方法中,@3.从内存/硬盘缓存加载。内存缓存也分了两级:
- ActiveResources。活跃缓存池,正在使用的EngineResource对象,弱引用缓存。资源释放时缓存进入LruResourceCache
- LruResourceCache。内存缓存池,LruCache管理的EngineResource缓存。获取缓存成功时,从LruResourceCache移除该缓存,并添加到ActiveResources
key-EngineKey:
EngineKey封装了加载图片的uri、宽高等信息。
value-EngineResource:
EngineResource是个泛型类封装了图片资源(根据ViewTarget的类型可以是Drawable也可以是Bitmap),及引用计数acquired,这个引用计数很关键,缓存被引用时计数+1。当某个图片加载完毕时尝试释放资源时要根据该引用来决定是否可以释放。
private EngineResource> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
//@6.活跃缓存-ActiveResources
EngineResource> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
//@7.若从活跃缓存池activeResources中获取资源失败
//则从内存缓存-LruResourceCache中获取
EngineResource> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
@6.活跃缓存-ActiveResources。通过持有当前使用的EngineResource对象的弱引用来实现缓存。若命中则active.acquire()引用计数+1。
private EngineResource> loadFromActiveResources(Key key) {
EngineResource> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
@7.内存缓存-LruResourceCache。通过LruCache实现的图片资源缓存。这个LruResourceCache是在初始化时GlideBuilder默认生成的,也可以设置自定义内存缓存
Glide build(@NonNull Context context) {
。。。//代码省略
//内存缓存
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
//硬盘缓存,具体实现是其DiskLruCacheWrapper,后面会讲到
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
。。。//代码省略
}
private EngineResource> loadFromCache(Key key) {
//从LruResourceCache中获取resource,并从该缓存中移除
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();//引用计数+1
activeResources.activate(key, cached);//存入活跃缓存池
}
return cached;
}
若两级内存缓存获取资源失败,再回到第3节中@4处发起获取资源任务waitForExistingOrStartNewJob
private LoadStatus waitForExistingOrStartNewJob(。。。//形参省略) {
。。。//代码省略
//构建加载任务
EngineJob engineJob =
engineJobFactory.build(。。。//实参省略);
//构建任务的子任务解析图片任务
DecodeJob decodeJob =
decodeJobFactory.build(。。。//实参省略);
jobs.put(key, engineJob);
//添加任务回调
engineJob.addCallback(cb, callbackExecutor);
//@8.通过线程池开启任务
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
@8.通过线程池开启任务。这里开始调用链路太长了,这里按下⏩挑重点来讲吧,避免钻入源码黑洞。。。:
- -->Engine.waitForExistingOrStartNewJob
- -->EngineJob.start
- -->DecodeJob.run-->runWrapped-->runGenerators
- -->DataFetcherGenerator.start
- (a)ResourceCacheGenerator.startNext
- (b)SourceGenerator.startNext
在这里打断一下,开始出现了分支。
(a)硬盘缓存:ResourceCacheGenerator.startNext从硬盘缓存中
。在runGenerators这个函数中出现了硬盘缓存逻辑。Glide默认DiskLruCacheWrapper实现,原理类似DiscLruCache同样使用LRU策略管理缓存文件,且可以通过设置DiskCacheStrategy来决定缓存原图文件还是解码后的图片。
(b)网络加载:若硬盘缓存获取失败,SourceGenerator.startNext通过网络请求下载图片,具体实现是HttpUrlFetcher。HttpURLConnection建立网络连接,下载图片、解码、压缩、并根据缓存策略存储到硬盘缓存中、更新到内存缓存。这部分涉及代码较多就不展开讲了。。。
- -->DataFetcherGenerator.onDataReady
- -->DecodeJob.onDataFetcherReady
- -->EncodeJob.onResourceReady。在这里会更新图片资源到活跃内存缓存activeResources
后面的流程就回到了第3节@5中了,不再赘述
- -->ResourceCallback.onResourceReady
- -->SingleRequest.onResourceReady
- -->ImageViewTarget.onResourceReady
- -->DrawableImageViewTarget.setResource
- -->ImageView.setImageDrawable
总结下Glide的三级缓存策略:
- (1)Engine.load中先从内存缓存中加载图片loadFromMemory
- 先从actives活跃缓存中获取
- 获取失败从内存缓存cache中获取,同时将缓存从cache中移到actives中
- (2)内存缓存获取失败,则构建EngineJob并执行start
- (3)执行DecodeEngine.run通过ResourceCacheGenerator从硬盘缓存中读取
- (4)读取失败则通过SourceGenerator执行获取任务,下载网络图片
- (5)下载成功后经过解码、压缩后,更新硬盘缓存及活跃缓存actives
- (6)回调ImageViewTarget最终设置并显示图片到目标ImageView
5-总结
最后总结下Glide.with(this).load(url).into(mainPic)
这行代码中Glide从初始化到加载显示图片的整个流程: