前言
这次的源码分析所对应的Glide版本为4.11.0
,这是我为数不多的仔细看比较复杂的源码框架,上一次看还是大二的时候看ARouter,不过那时水平有限(也不知道现在比那时候有没有变强)。这次的源码分析写了4-5天左右,自己实践了Glide的一些功能+阅读文档+看源码,然后再总结成了这篇阅读笔记,也确实填充了我的一些知识空白。这篇阅读笔记内容比较详细,而且配合了注释+图片,看的朋友们应该也比较好懂。
本文分成以下几个部分,针对我们常用的Glide.with().load().into()的写法,以网络请求为例子进行了分析。我写完了这篇之后,也看了一些关于Glide的面经,稍加整理和思索基本上可以回答出来问题。如果读者觉得不错的话,就点个赞吧,有问题我们可以在留言区探讨探讨~(希望我能回答出来hh
Part I 请求铺垫
常规使用方式
日常开发中使用Glide一般用于给ImageView或者其子类加载图片资源,或者是使用Glide下载图片。
本文以首次加载网络中的一张图片到ImageView中为例分析Glide的源码。之所以强调首次是因为Glide中存在图片复用的策略。
// 一个比较简单的例子
Glide.with(this) // 返回RequestManager对象
.load(imagePath) // 返回RequestBuilder对象
.into(imageView)
// 一个稍微复杂的配置的例子
Glide.with(this)
.load(imagePath)
.placeholder(R.color.black) // 返回BaseRequestOptions
.error(R.drawable.ic_launcher_background) // 返回BaseRequestOptions
.transform(MultiTransformation(FitCenter(), CenterCrop()))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(imageView) // 这里还可以指定Target
val resultBitmap = Glide.with(this)
.asBitmap()
.load(imagePath)
.submit()
.get()
复制代码
思考如下问题:
- Glide使用过程中需要注意线程切换问题吗?
- 作为一个流式调用,很难不联想到如RxJava Kotlin中的中间操作和终止操作。Glide的流式调用是否可以划分成“中间操作”和“终止操作”呢?
- Glide的缓存机制的实现如何?Glide如何重用资源的?
Glide构造方法
Glide在使用时通常都是一个以Glide单例开始,Glide.with(context)之后开始进行请求加载过程。
所以我们先看看Glide中的构造函数。
// Glide#constructor(()
Glide(
@NonNull Context context,
@NonNull Engine engine,
@NonNull MemoryCache memoryCache,
@NonNull BitmapPool bitmapPool,
@NonNull ArrayPool arrayPool,
@NonNull RequestManagerRetriever requestManagerRetriever,
@NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
int logLevel,
@NonNull RequestOptionsFactory defaultRequestOptionsFactory,
@NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
@NonNull List<RequestListener<Object>> defaultRequestListeners,
boolean isLoggingRequestOriginsEnabled,
boolean isImageDecoderEnabledForBitmaps) {
// 初始化全局变量
// 如 this.engine = engine
.......
final Resources resources = context.getResources();
// Registry 类负责管理图像的编码解码等
registry = new Registry();
registry.register(new DefaultImageHeaderParser());
// Right now we're only using this parser for HEIF images, which are only supported on OMR1+.
// If we need this for other file types, we should consider removing this restriction.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
registry.register(new ExifInterfaceImageHeaderParser());
}
List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers();
// 初始化和图像采样解码相关的类
.....
// 不同类型的数据交给不同类型的工厂类初始化和处理
// 展示一下几个例子,其他的省去了
ResourceDrawableDecoder resourceDrawableDecoder = new ResourceDrawableDecoder(context);
ResourceLoader.StreamFactory resourceLoaderStreamFactory =
new ResourceLoader.StreamFactory(resources);
......
// 不同类型的图像(Bitmap File)有不同的Decoder,这些Decoder都在Registry中注册
......
// 对于不同的Target类型产生不同的Target对象
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
//Glide单例中的属性全部都托付给GlideContext管理
glideContext =
new GlideContext(
context,
arrayPool,
registry,
imageViewTargetFactory,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
engine,
isLoggingRequestOriginsEnabled,
logLevel);
}
复制代码
Glide构造函数签名、构造方法中涉及到的重要的类。
类型 | 作用 |
---|---|
Engine | 管理、加载活动资源和缓存资源 |
MemoryCache | 使用LRU缓存算法缓存资源实现 |
BitmapPool | 回收重用Bitmap资源 |
ArrayPool | 回收和重用资源 |
RequestManagerRetriever | 获取RequestManager对象 |
RequestOptionsFactory | RequestOptions的工厂类 |
RequestRegistry | decode encoder transcoder等等注册管理 |
GlideContext | 作为一个全局的Glide上下文对象 向外提供加载资源需要的参数 |
Glide单例的获取
// Glide#get
public static Glide get(@NonNull Context context) {
if (glide == null) {
//和GlideModule有关
.....
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
复制代码
Bitmap 重用和回收的实现
Bitmap重用和回收主要依赖的是BitmapPool对象。在Glide构造方法中,BitmapPool主要和encoder,decoder,transcoder,DownSampler这些图像处理相关的类打交道。由于我本身对图像处理这块暂时不太感兴趣,于是就看看BitmapPool管理bitmap的逻辑。
LruBitmapPool
BitmapPool主要实现类是LruBitmapPool,一个使用了Lru算法管理Bitmap对象的类。按照Lru算法的思想,一个新的Bitmap对象被添加到链表中,并且链表的大小超过了maxSize,最不常用的Bitmap会被移除:
// LruBitmapPool#put(Bitmap)
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
// 调用Bitmap.recycle()时会使用native方法清理Bitmap引用指向的像素。
// recycle方法主要是作为一个标记使用,方便GC回收。
// 如果Bitmap已经回收了 isRecycled返回true
if (bitmap.isRecycled()) {
throw new IllegalStateException("Cannot pool recycled bitmap");
}
// isMutable()表示Bitmap是可以被复用的
if (!bitmap.isMutable()
|| strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
//....
// 如果:
// 1. 当前的Bitmap 不能被复用
// 2. 当前的Bitmap的大小过大(bytes超过了maxSize)
// 3. 不满足相关Bitmap设置
// 直接标记bitmap回收,不放入BitmapPool中
bitmap.recycle();
return;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
// ....
dump();
evict();
}
复制代码
另外还有一个相关类是ArrayPool,思想和这个类似。
Glide.with()
RequestManager和RequestManagerRetriever
Glide.with()返回的是一个RequestManager对象,这个对象的取用是通过RequestManagerRetriever实现的。在看具体的代码之前,我已经归纳了这两个类的职责:
- RequestManager职责:
- 监听Activity声明周期回调
- 管理和启动Request
- RequestManagerRetriever职责:
- 通过不同的重载函数返回RequestManager对象
通过RequestManagerRetriever返回RequestManager对象
通过RequestManagerFragment监听生命周期
主要是通过get()一系列的重载函数,如图所示:
下面选取RequestManager#get(Activity)和RequestManager#get(Context)进行展示
RequestManager#get(Activity)
public RequestManager get(@NonNull Activity activity) {
// 如果在UI线程:
// 这里的是否在UI线程是通过looper来判断,如果传入的context是ApplicationContext
// 自然不在"UI线程"
if (Util.isOnBackgroundThread()) {
// --- 备注2: RequestManager#get(Context) ---
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
// --- 备注1:构建RMFragment来监听Activity的生命周期回调 ---
return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
复制代码
一般来说,我们使用Glide的时候并不需要在Activity的生命周期回调函数中注册Glide.clear(),这是因为Glide自身通过RequestManagerRetriever返回的RequestManager需要具备能够监听生命周期回调的能力。这样实现的目的是为了Glide能够方便的回收资源。当Activity被销毁的时候,自然不需要额外的加载,更不需要加载好的Bitmap在内存中占用资源。
//RequestManagerFragment 相关的生命周期方法
@Override
public void onStart() {
super.onStart();
// lifeCycle是ActivityFragmentLifecycle的实例,用于提示RMFragment绑定的Activity生命周期回调
lifecycle.onStart();
}
复制代码
监听方式的实现是通过将RequestManager和一个没有界面的(view-less)的Fragment:RequestManagerFragment(后文简写为RMFragment)绑定。
// --- 备注1:构建RMFragment来监听Activity的生命周期回调 ---
private RequestManager fragmentGet(
@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
// view-less RMFragment
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
// 绑定RMFragment
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
复制代码
RMFragment内部实现了对于它所以来的Activity生命周期的监听。通过Factory构建的RequestManager这样一来就有了生命周期感知力。
如果传入的是一个context,就转入备注2的get(Context):
// --- 备注2: RequestManager#get(Context) ---
// RequestManagerRetriver#get(context)
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
// Util.isOnMainThread() 是否是在主线程上被调用
// 根据不同的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
// Only unwrap a ContextWrapper if the baseContext has a non-null application context.
// Context#createPackageContext may return a Context without an Application instance,
// in which case a ContextWrapper may be used to attach one.
&& ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
return get(((ContextWrapper) context).getBaseContext());
}
}
// 使用单例模式返回一个RequestManager对象
return getApplicationManager(context);
}
复制代码
RequestManagerRetriver#get(context)主要思想是对所有的Context实例进行类型上的匹配。主要的情况分成有UI的context和没有UI的context,后者指的是application context。对于这样的context,通过RequestManagerRetriever返回一个applicationManager单例:
// RequestManagerRetriever#getApplicationManager
private RequestManager getApplicationManager(@NonNull Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
//.....
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
复制代码
如果是UI相关的context,每次都会构造一个Glide实例,这也并不难理解,毕竟UI相关的context是有生命周期的。
// RequestManagerRetriever#supportFragmentGet
private RequestManager supportFragmentGet(....) {
//....
if (requestManager == null) {
//...
// 构造一个实例对象
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
复制代码
Glide.with().asBitmap().load()
需要十分注意的是Glide.with()返回的是一个RequestManager对象,之后的asBitmap(), load(), transform()都返回的是RequestBuilder对象。
RequestBuilder
RequestBuilder本身是BaseRequestOptions的子类
RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
复制代码
transform(), override(),placeholder()是RequestBuilder从父类继承而来的方法。
构建这个父子类的考虑,我想也是因为需要让RequestBuilder聚焦于自己的单一职责。
RequestBuilder的主要职责:
- 对于资源类型进行转化,通过各类asXXX:asBitmap(), asFile()
- 加载资源:load(), preload(), submit()
- 展示资源:into()
还有可以使用transistion进行过渡。
load相关方法
RequestBuilder#load方法有很多的重载函数,都是大同小异。实际上load方法并没有做实质性的工作,只是对于load(model)中接受的参数model,都会上转型成Object类存储起来。
举一个简单的例子:
// RequestBuilder#load(File file)
public RequestBuilder<TranscodeType> load(@Nullable File file) {
return loadGeneric(file);
}
// RequestBuilder#loadGeneric
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
复制代码
RequestOptions
BaseRequestOptions
RequestBuilder 继承了BaseRequestOptions中调用图像的裁减变换方法,具体实现需要看看BaseRequestOptions中:
//BaseRequestOptions#centerCrop()
public T centerCrop() {
//这里通过策略模式实现图片的变化
return transform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop());
}
复制代码
Glide.with().load().into()
真正的开始加载的方法被放在了into中。into方法一般来说使我们常用的操作的重点,在RequestBuilder中有很多into方法的重载。我们选择两种常用的具体看一下:
// 1. RequestBuilder#into(ImageView) 加载对象到ImageView上
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
// 一定需要是在主线程上
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
// .....
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
// 2. RequestBuilder#into(Target) 加载对象到Target上
// (可能是我们自定的Target,也可能使用一些Target API
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
return into(target, /*targetListener=*/ null,
// 指定回调的线程为主线程
Executors.mainThreadExecutor());
}
复制代码
还有其他的重载方法,但是大体的操作和思路都是类似的。这些重载方法都指定了Executor为主线程,并且这些方法条条大路通罗马,都会最终调用这个重载:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
// 1. 构建Request,注意callbackExecutor为主线程的Executor
Request request = buildRequest(target, targetListener, options, callbackExecutor);
// 2. 检查对应的Target的Request能否复用
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
// 3. 可以复用的话,begin
previous.begin();
}
return target;
}
// 4. 如果不能复用,绑定Tracker,构建请求
// TargetTracker和生命周期回调相关。
requestManager.clear(target);
target.setRequest(request);
// 这里最终还是会调用begin
requestManager.track(target, request);
return target;
}
复制代码
从into的代码中可以得出以下的结论
- Request会被TargetTrack追踪。TargetTracker实现了LifecycleListener,会监听生命周期回调。
- Request 对象和Target对象绑定,相同的Target的Request是相同的。
- Request绑定的callbackExecutor,也就是执行完成后的回调线程池是Executors.mainThreadExecutor()。
Target
返回的Target对象也非常重要,Glide通过回调可以操作加载好的资源设置到ImageView中。举Target的一个实现类ImageViewTarget为例:
// ImageViewTarget#onResourceReady
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
// 设置资源到ImageView
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
复制代码
当回调onResourceReady被触发时,最终会转到BitmapImageViewTarget(ImageViewTarget)的子类中设置Bitmap:
// BitmapImageViewTarget#setResource
@Override
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource);
}
复制代码
SingleRequest#begin
从上文into方法中会转到SingleRequest的begin:
// 3. 可以复用的话,begin
previous.begin();
复制代码
作为一个接口,Request的一个实现类是SingleRequest。SingleRequest的职责就是加载资源(Resource)到目标(Target)中。SingleRequest定义了一系列的枚举方法标识当前资源的加载状态:
// Request已经创建,但是还没有执行
PENDING,
// 获取资源的过程中
RUNNING,
// 等待Target中定义的callback被回调,用于确定图片的大小
WAITING_FOR_SIZE,
// 图片执行完成
COMPLETE,
// 失败
FAILED,
// 用于使用placeholder占位符清除了Request,在这种情况下可能需要重启Request
CLEARED,
复制代码
理解了这些枚举的意思,可以方便我们更好的阅读begin的源代码:
// SingleRequest#begin
public void begin() {
synchronized (requestLock) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
// ......
// 1. 不能begin正在运行的Request
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// 2. Resource获取完成
if (status == Status.COMPLETE) {
// onResourceReady会对资源resource进行检查
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// 3. 图片大小确定会调用Engine#load方法加载资源
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
// .......
}
}
复制代码
使用Glide加载一张网络上的图片使我们最常用场景之一,这个过程会被Engine#load方法处理。需要我们注意的是如下几个参数:
engine.load(
// GlideContext类型的变量,管理了很多和加载配置相关的信息
glideContext,
// model Object 类型
model,
//.....
// ResourceCallback资源加载后的回调,SingleRequest实现了它,这意味着资源加载好了之后会将控制权交还给SingleRequest中的方法
this,
// Executors.mainThreadExecutor()
callbackExecutor);
复制代码
整体的调用链非常长,我归纳在了这张图片中,我们详细看看。
Part II 加载开始
Engine#load
从Request#begin之后的下一站我们就回来到Engine#load
Glide最为经典的部分就是将缓存模型分成了三个部分:
- 活动资源 (Active Resources) – 现在是否有另一个 View 正在展示这张图片?
- LRU内存缓存 (Memory cache) – 该图片是否最近被加载过并仍存在于内存中?
- 资源类型(Resource) – 该图片是否之前曾被解码、转换并写入过磁盘缓存?
- 数据来源 (Data) – 构建这个图片的资源是否之前曾被写入过文件缓存
后面两个部分是硬盘缓存我们可以看成一个部分;第一第二都是在内存中的,我们统称为内存缓存。
Glide会首先看内存缓存(活动缓存和Lru缓存)中是否存在这样的资源,如果没有再去网络上请求。Glide对于内存缓存的处理后面的章节会详细讨论,这里先注重网络请求的过程。
具体对应的函数如表所示:
Engine#load包含了从内存中寻找(loadFromMemory)和从网络中寻找(waitForExistingOrStartNewJob)。先看看Engine#load
public <R> LoadStatus load(......) {
// ......
// 构造key
EngineKey key =
keyFactory.buildKey(........);
// 查找内存中是否有这样的资源
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
// 如果没有,再去网络上查找
return waitForExistingOrStartNewJob( ...... ;
}
}
复制代码
Engine#waitForExistingOrStartNewJob
我们假设我们现在是第一次加载,内存缓存、硬盘缓存中不存在我们需要的资源,我们需要去网络上加载资源。
// Engine#waitForExistingOrStartNewJob
private <R> LoadStatus waitForExistingOrStartNewJob(....) {
// 如果EngineJob存在
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
// ....
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(....);
DecodeJob<R> decodeJob =
decodeJobFactory.build(.....);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
// 执行decodeJob
engineJob.start(decodeJob);
///....
return new LoadStatus(cb, engineJob);
}
复制代码
Engine#waitForExistingOrStartNewJob首先会看看是否存在EngineJob。存在的依据是看看是否在Map中存在相同的key。EngineKey的参数很多,稍微修改一些都会被认为是不同的Key,导致重新请求
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
复制代码
这里应该是考虑相同的EngineJob是否可以复用。如果不能复用,使用线程池执行DecodeJob:
EngineJob#start
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
// 选择从磁盘还是从活动资源中解码
// 如果是后续请求,且在磁盘中有缓存,就会从磁盘中拿取
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
// 最终会调用run方法
executor.execute(decodeJob);
}
复制代码
DecodeJob#run
线程池最终会调用DecodeJob中的run方法。在分析具体的代码之前,需要理解DecodeJob的职责:
DecodeJob的职责
A class responsible for decoding resources either from cached data or from the original source and applying transformations and transcodes.
根据文档中的描述,DecodeJob的具体职责是解码缓存(cache)和从原始来源处的资源,并且实现过渡(transformation)和transcode(转码)
class DecodeJob<R>
implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable
复制代码
DecodeJob需要解码的资源应该也在run方法中的调用链上:
// DecodeJob#run
public void run() {
// 获取数据的类
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (CallbackException e) {
// 异常及其处理
}
}
复制代码
沿着调用链往下,我们最终可以得到SourceGenerator#startNext方法,我们先简单看看源码:
SourceGenerator#startNext
// SourceGenerator#startNext
// startNext的目标是成功使用DataFetcher进行请求数据
// 成功的话返回true
public boolean startNext() {
//. ....
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
// 1. 通过ModelLoader构建LoadData
// --- 备注1 ---- Glide如何选择具体的ModelLoader对象?
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
// 2. 进行真正的请求过程
startNextLoad(loadData);
}
}
return started;
}
复制代码
startNext的核心思想是使用一个while寻找找到合适的ModelLoader来建立LoadData,所以它通过引入以下角色完成了一次请求的过程:
- DataFetcher
- ModelLoader
DataFecther顾名思义,是完成网络请求的重要角色,后面会详细讨论。先看看ModelLoader:
ModelLoader
Glide设计ModelLoader的作用就是为了将复杂的数据类型转换成DataFetcher处理的LoadData。ModelLoader本质上是创建LoadData的一个工厂。按照上面例子中的假设,我们还是通过一个http url(String)进行网络请求,HttpGlideUrlLoader作为ModelLoader的实现类回去实现相关的方法:
public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream>
复制代码
目的非常纯粹,就是将GlideUrl包装的String url对象转换成Decoder能够处理的InputStream。
// HttpGlideUrlLoader#buildLoadData
public LoadData<InputStream> buildLoadData(
@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
// spent parsing urls.
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
// 返回构建的LoadData对象,并且配置了一个DataFetcher的实现类
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
复制代码
ModelLoader对象的匹配
我们返回上面的备注1,看看Glide如何匹配对应的ModelLoader对象:
// --- 备注1 ---- Glide如何选择具体的ModelLoader对象?
loadData = helper.getLoadData().get(loadDataListIndex++);
复制代码
List<LoadData<?>> getLoadData() {
// .....
//1. 从glideContext中寻找可能的ModelLoader
// glideContext是所有相关对象的管理上下文,是在Glide构造器中创建的,前面已经提及过
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
// 2. 面所说的buildLoadData方法构建LoadData对象
LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
// ...
}
}
return loadData;
}
复制代码
ModelLoader的匹配沿着调用链继续寻找,可以在ModelLoaderRegistry中找到结果
public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
/// ......
for (int i = 0; i < size; i++) {
// ...
// 如果ModelLoader能够handle对应的Model(这里会转换成GlideUrl包装的对象
if (loader.handles(model))
// ...
}
复制代码
SourceGenerator#startNextLoad
我们分析完成SourceGenerator#startNext之后,按着主线继续往下前行,就会到真正的启动load的方法:startNextLoad
// SourceGenerator#startNextLoad
private void startNextLoad(final LoadData<?> toStart) {
loadData.fetcher.loadData(
helper.getPriority(),
new DataCallback<Object>() {
@Override
public void onDataReady(@Nullable Object data) {
if (isCurrentRequest(toStart)) {
onDataReadyInternal(toStart, data);
}
}
@Override
public void onLoadFailed(@NonNull Exception e) {
if (isCurrentRequest(toStart)) {
onLoadFailedInternal(toStart, e);
}
}
});
}
复制代码
方法主要是使用LoadData绑定的Fetcher对象进行数据的抓取,针对成功和失败两种情况进行回调。
这样我们就来到的我们最后的环节,使用HttpUrlFetcher进行请求:
HttpDataFetcher#loadData
// HttpDataFetcher#loadData
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
// 处理重定向的情况
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
// 成功回调
// 这里的callback是DataCallback的实现,这个方法由上文中的
// SourceGenerator中的匿名内部类实现
callback.onDataReady(result);
} catch (IOException e) {
// ....
// 异常回调
callback.onLoadFailed(e);
} finally {
// ...
}
}
}
复制代码
请求图片的过程中可能会出现重定向的问题,而且这里的LoadData方法其实也只是loadDataWithRedirects的包装,具体的请求过程分析如下:
HttpUrlFetcher#loadDataWithRedirects
// HttpUrlFetcher#loadDataWithRedirects
// 使用HttpUrlConnection加载并且输出InputStream
private InputStream loadDataWithRedirects(
URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
// 不可有太多的重定向,出现重定向会递归调用,这个也是为了防止StackOverflow
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// ......
urlConnection = connectionFactory.build(url);
// ..... 省略了一些关于HttpUrlConnection的配置代码
// 连接
urlConnection.connect();
// 获取输入流
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
//......
// 如果返回了成功的状态代码
if (isHttpOk(statusCode)) {
// 获取InputStream
return getStreamForSuccessfulRequest(urlConnection);
//如果状态码的类型和重定向有关
} else if (isHttpRedirect(statusCode)) {
// 检查和获取重定向url有无问题
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
// 清理资源
// 关闭资源 inputStream.close() urlConnection.disconnect()
cleanup();
// 递归调用
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
throw new HttpException(statusCode);
} else {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
复制代码
当请求成功时,Glide就会回调到SourceGenerator的匿名内部类Callback中,将返回的InputStream进行解码。具体的内容这里就不分析了,
总结一下
上面的请求过程非常长,有点复杂,我使用一个图来总结它在过程中的主要调用逻辑:
请求的入口是RequestBuilder#into,通过Request#begin开始请求。在Request中会首先标记标记当前的请求状态为PENDING,也就是构建好了请求,但是还未开始。然后再进入Engine#load,在Engine#load中会判断是要加载内存中的(活动资源 Cache资源)资源,还是加载磁盘中,亦或是开启新任务。准备好了之后就会开启子线程处理这个任务,并且回到begin。当前请求状态是RUNNING。然后就等让Target等待结果,在这个过程中可以设置ImageView的;placeholder。
Glide缓存分析
内存两级缓存的配合策略
loadFromMemory()首先会去ActiveResource(活动资源)中寻找资源,如果Active中不存在,再会去Cache(缓存)中寻找。这两个缓存的配合关系如图所示:
Case#1: 如果活动资源中直接有需要的Key对应的资源,直接从活动资源里面拿
Case#2:如果活动资源中没有,从Cache中拿,并且将这个资源获取之后“激活”,放入活动资源。
活动资源和LRU资源都使用了引用计数的方式。当acquired计数器变成0,活动资源中的资源就会退到LRU资源中。
synchronized void acquire() {
// .....
++acquired;
}
复制代码
之所以在内存缓存上设计这样的两级缓存,个人是这样理解的:
如果只有Lru缓存,最不经常使用的资源,如图中的1,会随着后面资源的加入被移除,如果需要使用,又需要重新请求。设计活动资源的目的是表示在这里的资源正在被使用中,当这个资源需要的时候,可以从LRU中移除,减轻LRU缓存的压力。
loadFromMemory
// Engine#loadFromActiveResources(key)
private EngineResource<?> loadFromActiveResources(Key key) {
// activeEngineResource是为了方便Resource资源被回收
// Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>()
// 资源的取用直接通过键值对进行
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
// Engine#loadFromCache(key)
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
// 如果在缓存中存在,需要将这个资源“激活”到活动资源中
activeResources.activate(key, cached);
}
return cached;
}
复制代码
Engine#loadFromActiveResources(key) 中的activeResources是ActiveResource的一个实例。ActiveResource使用了Map将Key和Resource进行绑定
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
class ResourceWeakReference extends WeakReference<EngineResource<?>>
复制代码
ResourceWeakReference中的对象一般是Bitmap,这样的操作是为了当内存不足时方便回收被弱引用修饰的Bitmap。举个例子,当Lru中的资源被激活到活动资源之后,就会调用reset()方法,设置引用指向null,方便GC回收。
// ResourceWeakReference#reset
void reset() {
resource = null;
// native clear
clear();
}
复制代码
getEngineResourceFromCache()在具体实现中引入了MemoryCache。MemoryCache通过LRU算法管理放入的对象,当对象的数量超过规定的大小时,会抛弃最不常用的对象。
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache
复制代码
Glide的磁盘缓存策略
缓存策略 | 意义 |
---|---|
ALL | 对于远程的图片缓存Data和Resource,对于本地图片,只缓存Resource |
AUTOMATIC | 从磁盘上读取下载的原始数据 |
DATA | 只缓存Data |
NONE | Data和Resource都不缓存 |
RESOURCE | 只缓存Resource |
在我的理解中,Data表示的是没有被处理的原始的数据,Resource相对的表示的是处理之后的数据。
Glide默认使用的策略是AUTOMATIC,这种策略会加载没有被裁切,变换的原始的图片。当加载完了一次从网络上请求的图片之后,如果在磁盘缓存中能够”找到“缓存,则会从缓存中显示。正如上面所说的,在Engine#load方法中会首先构造EngineKey,EngineKey就是文件的标识,它由以下的内容组成:
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
复制代码
我这里Debug了来看看当前的例子中,这些参数具体的内容。
key的要求是很严格的,上面的参数,比如长宽,model(网络url)配置改变了都会导致EngineKey不同,从而不能从磁盘缓存中加载图片。磁盘缓存也是通过Lru算法支持的DiskLruCache类实现。
如果在磁盘中有缓存,在EngineJob#start方法中就会使用diskExecutor从磁盘中加载:
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
复制代码
总结
- Glide.with().load().into()使我们常用的三段式的使用方法,但是每一段返回的类型有区别,需要注意一下
- Glide对于Application Context返回的是一个全局的单例,对于每一个UI context,会首先检查是否存在创建好的Glide单例,如果没有,则创建并返回一个UI绑定的单例。
- Glide使用子线程池加载缓存,磁盘中的资源,在这个过程中,传递的回调线程池为主线程的线程池。并没有做线程切换,通过回调Target中的设置图片的方法,将加载好的资源放置到ImageView上。
参考内容
# 聊一聊关于Glide在面试中的那些事