glide-源码解析-2
glide-源码解析-2
本篇接篇1代码,来分析RequestManager.into方法
一、RequestManager.into(imageView)
我们可以大体推测下,into里面最终肯定会发送一个加载资源请求,请求数据并终转换成一个Drawable对象,最后将这个对象设置我们传入的imageView当中去,这样就实现了图片的加载。接下来看下glide是如何实现的?
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
* the view, and frees any resources Glide may have previously loaded into the view so they may be
* reused.
*
* @see RequestManager#clear(Target)
* @param view The view to cancel previous loads for and load the new resource into.
* @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link
* ImageView}.
*/
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
// 1. 运行线程校验,因为涉及到view的渲染,必须保证在UI线程
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
// 2. 获取imageview中的scaleType,并根据其值自动生成与其相匹配的requestOptions
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.
}
}
// 3. 构建imageViewTarget,requestOptions,UI线程执行器,传入into方法
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
这里面实现其实也是比较简单
- 校验工作
- 如果是不是运行的UI线程会抛出异常,因为涉及到view的渲染操作,所以必须保证在UI线程执行
- 校验传入的imageView是否空,view参数不能为空
- 生成一个与imageview.scaleType相匹配的请求配置信息
- 构建imageViewTarget、将2中的配置信息、UI线程调度器传入给into重载方法
1. 不得不说的Target
target翻译过来意思就是目标,在glide当中可以理解想把resource经过层层转换后,最终转换成Target需要消费的类型对象(resource),本例中是通过model(url)最终转换为Drawable对象
接下来看下buildImageViewTarget构建对象,最终调用ImageViewTargetFactory.buildTarget方法
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
public <Z> ViewTarget<ImageView, Z> buildTarget(
@NonNull ImageView view, @NonNull 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 DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
参数clazz就是Target想要接受消费resouce类型,也就是Drawable.class;这里glide返回的target其实是DrawableImageViewTarget对象,它其实实现了Target接口
Target是可以接受resource加载事件以及组件生命周期事件的对象,通常调用流程如下
我们有理由相信onResourceReady中方法提供了给我们想要的Drawable资源了,glide就是在这个方法中实现了将drawable设置到view当中去的;我们先看下DrawableImageViewTarget的父类ImageViewTarget,它实现了onResourceReady方法,
// ImageViewTarget.java
...
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
private void setResourceInternal(@Nullable Z resource) {
// Order matters here. Set the resource first to make sure that the Drawable has a valid and
// non-null Callback before starting it.
setResource(resource);
maybeUpdateAnimatable(resource);
}
protected abstract void setResource(@Nullable Z resource);
...
可以看到ImageViewTarget中最终调用了一个setResource抽象方法,子类DrawableImageViewTarget实现了它,
// DrawableImageViewTarget.java
@Override
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
可以清楚的看到DrawableImageViewTarget作为ImageViewTarget的子类承担了将target渲染到view上的职责,到了这里我们明白了resource(Drawable)是如何通过target渲染到mageview中去的。那问题来了,target的onLoadStarted、onResourceReady等方法到底有谁来触发的呢?
2. 谁触发了target的onXXX方法?
要回答这个问题, 我们需要回到之前的into方法中寻找答案,into方法调用了重载into方法
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
// 1. 校验target, model是否设置
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
// 2. 构建一个请求
Request request = buildRequest(target, targetListener, options, callbackExecutor);
// 3. 请求重复利用
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
// 4. 清理之前的请求
requestManager.clear(target);
// 5. 将新request和target绑定起来
target.setRequest(request);
// 6. 发起请求
requestManager.track(target, request);
return target;
}
其实归来起来就三点
-
校验操作
- 校验target不能为空
- 校验model不能为空(此处为url)
-
旧请求的复用
- 对于是相同请求的(请求的参数配置信息和大小是一样的视为相同请求)并且这个请求还没完成且可以内存复用
- 如果旧请求正在运行中,直接触发旧请求的begin方法
-
清理不能复用的旧请求
/** * Cancel any pending loads Glide may have for the target and free any resources (such as {@link * Bitmap}s) that may have been loaded for the target so they may be reused. * * @param target The Target to cancel loads for. */ public void clear(@Nullable final Target<?> target) { if (target == null) { return; } untrackOrDelegate(target); } private void untrackOrDelegate(@NonNull Target<?> target) { // isOwnedByUs为true表示清理成功了 boolean isOwnedByUs = untrack(target); // 如果清理不成功,需要glide继续做清理操作 Request request = target.getRequest(); if (!isOwnedByUs && !glide.removeFromManagers(target) && request != null) { target.setRequest(null); request.clear(); } synchronized boolean untrack(@NonNull Target<?> target) { Request request = target.getRequest(); // If the Target doesn't have a request, it's already been cleared. if (request == null) { return true; } // 此时拿到request不空,一定是旧的,需要自己动手清理 if (requestTracker.clearAndRemove(request)) { targetTracker.untrack(target); target.setRequest(null); return true; } else { return false; } } }
总结清理操作主要分如下几个步骤
- 将旧请求从RequestTracker中请求池中移除
- 中止旧请求,并释放所有相关资源
- 从TargetTracker.targets列表中移除target
- 解除target与request之间关联
-
发起新请求
-
target与新请求建立绑定关系
// ViewTarget.java @Override public void setRequest(@Nullable Request request) { setTag(request); } private void setTag(@Nullable Object tag) { isTagUsedAtLeastOnce = true; // tagId==R.id.glide_custom_view_target_tag view.setTag(tagId, tag); }
从ViewTarget类中可以看到是我们熟知的view.setTag方式来建立绑定关系,防止显示错位问题
-
调用requestManager.track方法(这部是重点)
-
3. RequestManager.track
重点方式就是在track方法
下面我们来看下其实现
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
代码非常简洁
可以看到第一步只是将target添加到targetTracker中的一个集合中去,这样做是为了能够让所有的target感知生命周期,以方便target在不同生命周期执行不同操作
重点是第二行它是请求的真正发起者,我们先了解下RequestTracker结构
RequestTracker这个类顾名思义它是请求的追踪者,负责追踪,取消,重启进行中,完成,失败的请求,这个类管理RequestManager中所有请求,它是非线程安全,必须在主线程使用
// RequestTracker.java
/** Starts tracking the given request. */
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
可以看到RequestTracker发起请求前是将其存储到requests集合中,如果页面处在非paused情况下那就发起请求;否则停止请求,并将其存放到待请求列表中,一旦页面处以可以请求状态直接恢复请求。
接下来我们了解下Request,它的职责是用来给target加载一个resouce
Glide中的Request是个接口,它有三个子类
我们以常用的SingleRequest为例来看下begin方法,里面就有我们期待的target.onLoadStarted、target.onResourceReady等方法
@Override
public void begin