Android图片加载框架,在android应用开发中是一个常见的话题。在12、13年的时候我记得可能用的最多的是XUtils的一套框架(更早之前叫aFinal框架),这个框架中提供imageUtils用于在android应用的开发中完成远程图片的加载。再后来呢,有Picasso、Fresco、Glide。而这几年的开发经验来看,Glide最为流行。不信,可以查看github上项目地址,分别对比对比watch数、fork数、star数,就能确定Glide确实最为流行。
如何对图片框架技术进行选型?
通过这种对比watch、fork、star数的方式,可以作为我们开发一款非特定需求应用的技术选型方式,毕竟对比清楚每一个框架之间的差异,是一件比较耗时、也是不容易的事情。除了图片加载框架,比如我们平常在为了解决项目中某些其它通用轮子问题的时候,我们可能逛街github,挑选免费的开源源码商品,这个时候,watch、fork、star数也可以作为我们选定某个框架的依据。一般即使是小众一点轮子开源源码star数也至少大于1000,我们才能考虑选型,star数代表阅读过此项目的同学的认可。另外一点,就是看issues的活跃度情况,比如有多少个issues,回复的及时性等情况如何。
上面一部分,介绍Android端图片加载框架的简单历程,插入了对于技术选型的非技术角度的手段的选型技巧概述。接下来我们还是开始介绍一下最流行的Glide图片加载框架。
Glide图片加载框架的优势有哪些?
Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection
的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。
虽然Glide 的主要目标是让任何形式的图片列表的滚动尽可能地变得更快、更平滑,但实际上,Glide几乎能满足你对远程图片的拉取/缩放/显示的一切需求。
以上是官方文档对Glide的介绍内容,末尾四个字硬气的很,“一切需求”。因为这篇是源码分析篇,不会过多介绍Glide图片加载框架的使用。
本篇文章的下文主要分为两个部分:(文章内容太长,回过头来补充说明下)
**第一部分:**按照图片的加载流程通读源码实现,并进行注释解释说明。(这部分内容比较枯燥,读者可以自己追踪源码走一遍流程。)
**第二部分:**对整体的图片加载流程进行概括,梳理核心流程,并绘制加载流程图。
另外,除了本篇的加载流程分析之外,会计划Glide源码在设计层面的一些解析和分析,包括Glide生命周期的设计分析、Glide缓存的设计分析。
源码版本为:4.12.0,为当前最新源码版本。
接下来我以一个最简单的Demo调用作为源码分析的入口,开始源码分析。
public class MainActivity extends AppCompatActivity {
String url = "";
ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
imageView = findViewById(R.id.img_glide);
//通过链式调用的方式加载一张图片
Glide.with(this).load(url).into(imageView);
}
//with、load、into三个方法分解后的调用
void glideLoadImg() {
RequestManager requestManager = Glide.with(this);
RequestBuilder requestBuilder=requestManager.load(url);
requestBuilder.into(imageView);
}
}
上述代码链式调用的代码,分解成了glideLoadImg()方法中的三行代码的调用,首先我们看Glide.with(this)方法的源码。
/**
* Glide是一个单例,简单的静态实现。作用是为RequestBuilder构建Requests和维护Engine、
* BitmapPool以及DiskCache和MemoryCache
*/
public class Glide implements ComponentCallbacks2 {
/**
* 获取的requestManager,在load之前需要调用,和Activity的lifecycle进行关联
*/
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
}
这个with方法的调用,最终返回的是一个RequestManager对象实例,RequestManager对象的获取是通过RequestManagerRetriever.get()方法获取。RequestManagerRetriever对象的职责就是用来创建和获取RequestMangaer对象的,在RequestManager内部通过RequestManagerFactor工厂进行最终的创建。
/**
* 创建一个新的requestManager或者从现有的Activity、fragment中检索一个已经存在ReqeustManager
*
*/
public class RequestManagerRetriever implements Handler.Callback {
private RequestManager fragmentGet(...) {
...
//获取已经存在的RequestManager
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
//没有获取到,通过工厂创建
if (requestManager == null) {
//获取单例的Glide对象
Glide glide = Glide.get(context);
//通过工厂创建requestManager
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
if (isParentVisible) {
requestManager.onStart();
}
//绑定
current.setRequestManager(requestManager);
}
return requestManager;
}
}
/**
*
* @see Glide#with(android.app.Activity)
* @see Glide#with(androidx.fragment.app.FragmentActivity)
* @see Glide#with(android.app.Fragment)
* @see Glide#with(androidx.fragment.app.Fragment)
* @see Glide#with(Context)
*/
public class RequestManager{
@Override
public synchronized void onStart() {
}
*/
@Override
public synchronized void onStop() {
}
@Override
public synchronized void onDestroy() {
}
}
RequestManager有上述5种重载方法可以得到,最终都会调用到fragmentGet()方法,整体流程是一致的。
RequestManager是一个Glide框架管理和开始请求的一个类。可使用Activity、Fragment以及关联其生命周期事件去智能的停止、开始、重新开始请求。可以理解成Glide的生命周期的管理类,在上述简化版的源码中,我们也看到了类似Activity生命周期的回调方法 onStart()、onStop()、onDestoy()。
requestBuilder对象的获取是通过RequestManager来完成的,看如下源码实现
public class RequestManager ...{
/**
* 这里面也是链式调用,相当于先调用了asDrawable()方法,然后调用requestBuilder.load(String)
* 返回一个新的RequestBuilder去加载使用指定的model去加载drawable
*/
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
}
上述asDrawable()、load()方法的调用都是返回一个RequestBuilder对象,最终链式调用的结果也是返回一个RequestBuilder的对象。load()方法的实现在RequestBuilder类源文件中。
public class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {
//返回的是一个加载给定字符串的requestBuilder对象
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
if (isAutoCloneEnabled()) {
return clone().loadGeneric(model);
}
this.model = model;
isModelSet = true;
return selfOrThrowIfLocked();
}
}
总之,从上述源码中可以看到,通过两次链式调用后,返回ReqeustBuilder实例,内部调用loadGeneric()对一些全局属性进行了设置,最终以RequestBuilder对象返回。
RequestBuilder:一个通用类,可以处理通用资源类型的设置选项和启动加载。说白了,为后续环节真正的发起图片加载做准备工作的。
into方法,是我们通过Glide加载图片调用的最后一个方法,with方法、load方法可以理解成都是为into方法的核心逻辑做准备的,into方法中会涉及到发起远程图片加载的核心逻辑。看RequestBuilder.into()方法的源码实现:
/**
* into方法接受的参数是imageView,imageView就是资源(url\uri...)要被载入的目标
* 取消任何现有的加载到视图中,并释放 Glide 之前可能加载到视图中的任何资源,以便它们可以被重用。
* Return:方法返回的ViewTarget是传入参数imageView的包装
*/
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
....
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// View ScaleType的设置
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
....
default:
// Do nothing.
}
}
//会继续调用下面的重载的into方法
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
//主线程池
Executors.mainThreadExecutor());
}
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
/**这个isModelSet是在load方法中设置为True的,如果未调用load方法直接调用into方法抛出异常
*我们自己在设计一些类中的方法时,如果需要做校验,也可以通过这种抛出异常的方法,来提醒调用者
*使用正确的方式调用*/
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
//构建Request
Request request = buildRequest(target, targetListener, options, callbackExecutor);
//获取当前目标对象上先前是否已存在的Request
Request previous = target.getRequest();
//如果这两个request相同&&不跳过内存缓存
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)
/**这个方法的细节,可以查看源码注释*/) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
//目标request可复用,直接返回
return target;
}
requestManager.clear(target);
//给目标设置新创建的Request
target.setRequest(request);
//通过RequestManager调用track方法
requestManager.track(target, request);
return target;
}
当我们调用RequestBuilder.into方法时,RuquestBuilder对象帮我们构造或是重用Request实例对象。最终将request实例通过requestManger.track()方法传入。接下来就重新回到RequestManger方法中来。
public class RequestManager...{
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
//看到这个方法,是不是感觉真实的请求调用就在此了
requestTracker.runRequest(request);
}
}
这里面仍然有一个问题,我们没有去分析,就是在上一段源码中,Request对象的构建过程是怎样的?
我们先先顺着看requestTracker.runRequest(request)的方法的实现:
public class RequestTracker {
//请求队列
private final Set<Request> requests =
Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
//等待队列
private final List<Request> pendingRequests = new ArrayList<>();
/** 开始追踪一个指定的请求 */
public void runRequest(@NonNull Request request) {
//将request添加到请求队列
requests.add(request);
if (!isPaused) {
//没有被pause,开始发起请求
request.begin(<