本文基于Glide 4.11.0
阅读前请参考Glide 源码分析解读-基于最新版Glide 4.9.0一文,该文章中,将Glide中各个部分的作用分析的非常好了。
Glide几乎是现在做Android图片加载的最佳选择了。如此优秀的一个框架是如何实现的呢?如果让我们自己来实现该怎么做呢?我们就通过自己实现一个低配版Glide的方式,来探究Glide中是如何实现的。
我们就称我们自己低配版Glide为Slide。那么Slide要实现哪些功能呢?简单来说,就是获取图片+界面显示。我们通过先构架大体框架,再分步丰富其中细节的方式,来构建Slide的整体结构。
1 | flowchart LR; |
1 | Glide.with(xxx).load(url).into(iv); |
这是Glide一个典型的最为简单的调用过程。那么在这个过程中发生了哪些事情呢?
我们可以通过这个链式调用的返回值发现,有如下过程:
1 | graph LR; |
Glide.with(xxx)发生了什么事?
阅读源码发现,Glide.with(xxx)
的最终实现类是RequestManagerRetriever.java类。继续跟踪,我们在这个类中,看到这样一个方法。
1 |
|
重点关注这个getSupportRequestManagerFragment方法。
1 | //getSupportRequestManagerFragment |
其实,这里是一个Glide检测到界面生命周期的关键了。Glide就是通过像当前Activity添加一个一个无UI的Fragment来探测生命周期的。
注意:在执行了添加fragment的语句
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss()
后,又马上通过handler发送了一个一个message,这里需要熟悉Handler机制才能理解,简单来说,就是添加fragment操作,实际上也是通过handler执行的,这是一个异步的过程,如何检测到fragment已经添加完成了呢?就是在commitAllowingStateLoss
后,马上去发送一条指定的消息,利用handler处理message的顺序性,来获知fragment已经添加完成。
经过添加SupportRequestManagerFragment后,我们获得了探测当前界面生命周期的能力。
继续阅读supportFragmentGet
方法代码,RequestManager是从SupportRequestManagerFragment拿到的,如果拿到的是空,则创建一个,设置到该fragment当中去。
RequestManager.load(xxx)发生了什么?
我们以load(url)
为例,来探究这部分代码。
这个方法,返回的是RequestBuilder这个类,看名字就知道,这是一个构建者模式中的Builder类,主要是在添加各种配置项,比如RequestOptions、RequestListener等。
RequestBuilder.into(iv)发生了什么?
其实,这里才是真正开始触发发起请求的地方。
RequestBuilder
我们把into(ImageView)
方法作为入口,一路跟踪,可以发现最终的实现是如下方法。
1 | private <Y extends Target<TranscodeType>> Y into( |
这个方法主要是做了以下事情:
- 是否已经有一个request在处理相同的请求,如果有,则判断是否正在运行,没有正在运行则开始运行;
- 如果没有一个request在处理此请求,则对target做一些清理操作,与之前的请求解绑,为当前target设置新的请求,然后requestManager开始追踪这个请求。
接下来我们就按照requestManager.track(target, request)
这段代码继续追踪。来到RequestManager的track方法。
RequestManager
1 | synchronized void track( { Target<?> target, Request request) |
这个方法很简单,只有两个方法。
TargetTracker
1 | private final Set<Target<?>> targets = |
这里把一个target存放在WeakHashMap中,因为target是与生命周期有关的东西,比如ImageView对应的ImageViewTarget,所以这么做是为了防止内存泄漏。
RequestTracker
1 | /** Starts tracking the given request. */ |
这里是将暂停的request加入到pendingRequests中去,如果不是暂停的request,则调用其begin方法。
我们查看Request类的子类,可以看到下图。
可以看到一共有4个类实现了Request类,其中FakeRequest类是用于测试的,不去考虑。其他三个类的作用如下:
- ThumbnailRequestCoordinator: 用来加载thumbnail;
- ErrorRequestCoordinator: 用来加载错误时候,展示错误状态;
- SingleRequest: 这才是用来加载目标图片的request。
我们重点去看SingleRequest的begin方法。
SingleRequest
1 | public void begin() { |
代码虽长,但是结构简单。主要做了以下事情:
- 检查model是否是空,model就是要加载的数据来源,比如url、resourceId、File等;
- 判断request状态,不能重新开始一个正在运行的请求;
- 判断request状态,如果是已经完成的请求,则说明资源已经存在,直接调用
onResourceReady
方法并返回; - 接下来就来到判断target尺寸的过程了,如果target尺寸已经确定,比如view尺寸measure结束后,则调用
onSizeReady
方法,注意:实际的网络请求就在这个onSizeReady方法中,因为只有target的尺寸确定了,才能进行请求并处理图片; - 如果尺寸未确定,则调用
target.getSize
方法去监听尺寸事件,具体可以参考ViewTarget#getSize
方法,这是一个通过onPreDrawListener来监听尺寸的; - 接下来回调
onLoadStarted
方法,并且显示加载过程状态。
我们着重看网络请求那个分支,也就是onSizeReady
方法。
1 |
|
关键代码来了!这个engine
就是Glide的核心。这个engine是在Glide初始化时候生成的一个实例。
Engine
Engine不只是用于加载图片,而是一个任务执行核心引擎,它要执行的不只是请求远程图片的任务,包括解码任务等,它执行的实际上是一个个的job。
跟踪上一阶段中的engine.load
方法,来到是这个方法的关键部分——调用waitForExistingOrStartNewJob
。
在这个方法中,主要做了以下事情:
1 | graph TD; |
EngineJob.start(decodeJob)
1 | public synchronized void start(DecodeJob<R> decodeJob) { |
这里执行的是decodeJob。
这里需要着重关注一点,就是
executor.execute(decodeJob)
的时候,就已经通过GlideExecutor的sourceExecutor.Builder创建了一个ThreadPoolExecutor,也就是在这里实现线程池异步执行任务。ThreadPoolExecutor并不是Glide提供的实现,而是在java.util.concurrent包下。
DecodeJob
DecodeJob是一个Runnable类,所以,我们查看其run方法。
接下来的调用路径参考下图。
1 | graph TD; |
经过这么长的调用链,我们终于来到了网络请求的部分,我们可以看到Glide原生使用的HttpURLConnection进行网络请求的。获取到InputStream后,在SourceGenerator中的cacheData方法进行缓存处理。
获取到数据后的处理
通过DataFetcherGenerator.FetcherReadyCallback
可以探知到数据获取成功或者失败,如果获取成功,则在DecodeJob#onDataFetcherReady
中处理。关键代码如下:
1 | public void onDataFetcherReady( |
更改任务状态,重新执行此任务,则重新执行到getNextGenerator
方法,此时则会返回DataCacheGenerator来处理从Disk缓存加载的任务。
获取图片
首先,图片来源有哪些?
- 资源图片:drawable, assets, raw, mipmap这些程序中自带的图片;
- 本地图片:本地存储设备上的图片;
- 远端图片:我们服务器或者来自第三方服务器的图片,通过URL来获取。这就需要异步网络请求,请求结束以后,要缓存图片,避免重复请求远端图片,造成时间、网络的浪费。
1 | graph LR; |
那么接下来,要丰富的细节,就来到了网络请求和缓存了。
网络请求
缓存
Author: boybeak