Glide 是 Android 端比较常用的图片加载框架,这里我们就不再介绍它的基础的使用方式。你可以通过查看其官方文档学习其基础使用。这里,我们给出一个 Glide 的最基本的使用示例,并以此来研究这个整个过程发生了什么:
Glide.with(fragment).load(myUrl).into(imageView);
上面的代码虽然简单,但是整个执行过程涉及许多类,其流程也比较复杂。为了更清楚地说明这整个过程,我们将 Glide 的图片加载按照调用的时间关系分成了下面几个部分:
with()
方法的执行过程load()
方法的执行过程into()
方法的执行过程- 阶段1:开启
DecodeJob
的过程 - 阶段2:打开网络流的过程
- 阶段3:将输入流转换为
Drawable
的过程 - 阶段4:将
Drawable
展示到ImageView
的过程
- 阶段1:开启
即按照上面的示例代码,先分成 with()
、load()
和 into()
三个过程,而 into()
过程又被细化成四个阶段。
下面我们就按照上面划分的过程来分别介绍一下各个过程中都做了哪些操作。
1、with() 方法的执行过程
1.1 实例化单例的 Glide 的过程
当调用了 Glide 的 with()
方法的时候会得到一个 RequestManager
实例。with()
有多个重载方法,我们可以使用 Activity
或者 Fragment
等来获取 Glide
实例。它们最终都会调用下面这个方法来完成最终的操作:
public static RequestManager with(Context context) {
return getRetriever(context).get(context);
}
在 getRetriever()
方法内部我们会先使用 Glide
的 get()
方法获取一个单例的 Glide 实例,然后从该 Glide 实例中得到一个 RequestManagerRetriever
:
private static RequestManagerRetriever getRetriever(Context context) {
return Glide.get(context).getRequestManagerRetriever();
}
这里调用了 Glide 的 get()
方法,它最终会调用 initializeGlide()
方法实例化一个单例的 Glide
实例。在之前的文中我们已经介绍了这个方法。它主要用来从注解和 Manifest 中获取 GlideModule,并根据各 GlideModule 中的方法对 Glide 进行自定义:
《Glide 系列-1:预热、Glide 的常用配置方式及其原理》
下面的方法中需要传入一个 GlideBuilder
实例。很明显这是一种构建者模式的应用,我们可以使用它的方法来实现对 Glide 的个性化配置:
private static void initializeGlide(Context context, GlideBuilder builder) {
// ... 各种操作,略
// 赋值给静态的单例实例
Glide.glide = glide;
}
最终 Glide 实例由 GlideBuilder
的 build()
方法构建完毕。它会直接调用 Glide 的构造方法来完成 Glide 的创建。在该构造方法中会将各种类型的图片资源及其对应的加载类的映射关系注册到 Glide 中,你可以阅读源码了解这部分内容。
1.2 Glide 的生命周期管理
在 with()
方法的执行过程还有一个重要的地方是 Glide 的生命周期管理。因为当我们正在进行图片加载的时候,Fragment 或者 Activity 的生命周期可能已经结束了,所以,我们需要对 Glide 的生命周期进行管理。
Glide 对这部分内容的处理也非常巧妙,它使用没有 UI 的 Fragment 来管理 Glide 的生命周期。这也是一种非常常用的生命周期管理方式,比如 RxPermission
等框架都使用了这种方式。你可以通过下面的示例来了解它的作用原理:
示例代码:使用 Fragment 管理 onActivityResult()
在 with()
方法中,当我们调用了 RequestManagerRetriever
的 get()
方法之后,会根据 Context 的类型调用 get()
的各个重载方法。
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)) {
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);
}
我们以 Activity 为例。如下面的方法所示,当当前位于后台线程的时候,会使用 Application 的 Context 获取 RequestManager
,否则会使用无 UI 的 Fragment 进行管理:
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
然后就调用到了 fragmentGet()
方法。这里我们从 RequestManagerFragment
中通过 getGlideLifecycle()
获取到了 Lifecycle
对象。Lifecycle
对象提供了一系列的、针对 Fragment 生命周期的方法。它们将会在 Fragment 的各个生命周期方法中被回调。
private RequestManager fragmentGet(Context context, FragmentManager fm,
Fragment parentHint, boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
然后,我们将该 Lifecycle
传入到 RequestManager
中,以 RequestManager
中的两个方法为例,RequestManager
会对 Lifecycle
进行监听,从而达到了对 Fragment 的生命周期进行监听的目的:
public void onStart() {
resumeRequests();
targetTracker.onStart();
}
public void onStop() {
pauseRequests();
targetTracker.onStop();
}
1.3 小结
经过上述分析,我们可以使用下面的流程图总结 Glide 的 with()
方法的执行过程:
2、load() 方法的执行过程
2.1 load() 的过程
当我们拿到了 RequestManager
之后就可以使用它来调用 load()
方法了。在我们的示例中传入的是一个 url 对象。load()
方法也是重载的,我们可以传入包括 Bitmap, Drawable, Uri 和 String 等在内的多种资源类型。示例中会调用下面的这个方法得到一个 RequestBuilder
对象,显然这是一种构建者模式的应用。我们可以使用 RequestBuilder
的其他方法来继续构建图片加载请求,你可以通过查看它的源码了解 Glide 都为我们提供了哪些构建方法:
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
在 RequestBuilder
的构造方法中存在一个 apply()
方法值得我们一提,其定义如下。从下面的方法定义中可以看出,我们可以通过为 RequestBuilder
指定 RequestOptions
来配置当前图片加载请求。比如,指定磁盘缓存的策略,指定占位图,指定图片加载出错时显示的图片等等。那么我们怎么得到 RequestOptions
呢?在 Glide 4.8.0 中的类 RequestOptions
为我们提供了一系列的静态方法,我们可以这些方法来得到 RequestOptions
的实例:
public RequestBuilder<TranscodeType> apply(RequestOptions requestOptions) {
Preconditions.checkNotNull(requestOptions);
this.requestOptions = getMutableOptions().apply(requestOptions);
return this;
}
回过头来,我们可以继续跟踪 load()
方法。其实,不论我们使用了 load()
的哪个重载方法,最终都会调用到下面的方法。它的逻辑也比较简单,就是将我们的图片资源信息赋值给 RequestBuilder
的局部变量就完事了。至于图片如何被加载和显示,则在 into()
方法中进行处理。
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
2.2 小结
所以,我们可以总结 Glide 的 load()
方法的执行过程如下。也就是使用 RequestManger
得到一个 RequestBuilder
的过程:
3、into() 方法的执行过程
考虑到 into()
方法流程比较长、涉及的类比较多,我们按照图片加载的过程将其分成四个阶段来进行介绍。
第一个阶段是开启 DecodeJob
的过程。DecodeJob
负责从缓存或者从原始的数据源中加载图片资源,对图片进行变换和转码,是 Glide 图片加载过程的核心。DecodeJob
继承了 Runnable
,实际进行图片加载的时候会将其放置到线程池当中执行。这个阶段我们重点介绍的是从 RequestBuilder
构建一个 DecodeJob
并开启 DecodeJob
任务的过程。即构建一个 DecodeJob
并将其丢到线程池里的过程。
第二个阶段是打开网络流的过程。这个阶段会根据我们的图片资源来从数据源中加载图片数据。以我们的示例为例,在默认情况下会从网络当中加载图片,并得到一个 InputStream
.
第三个阶段是将输入流转换为 Drawable
的过程。得到了 InputStream
之后还要调用 BitmapFactory
的 decodeStream()
方法来从 InputStream
中得到一个 Drawable
.
第四个阶段是将 Drawable
显示到 ImageView
上面的过程。
3.1 阶段1:开启 DecodeJob 的过程
3.1.1 流程分析
我们继续沿着 into()
方法进行分析。
into()
方法也定义在 RequestBuilder
中,并且也是重载的。不论我们调用哪个重载方法都会将要用来显示图片的对象封装成一个 Target
类型。Target
主要用来对用来显示图片的对象的生命周期进行管理。当我们要将图片加载到 ImageView 的时候,最终会调用下面的 buildTarget()
方法来讲我们的 ImageView 封装成一个 ViewTarget
,然后调用 into()
的重载方法进行后续处理:
public <Z> ViewTarget<ImageView, Z> buildTarget(ImageView view, Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new