文章目录
Glide 是目前非常流行的图片加载第三方开源库,而且功能极其强大,内部代码复杂程度也极其大。经过一番心理斗争决定还是要好好学习Glide的部分源码设计。
因为Glide非常的强大且复杂,所以我挑选了自己感兴趣的几个点学习研究:
- Glide 加载图片的整体流程
- Glide 的缓存池
- Glide 怎么判断图片是有效
- Glide 怎么计算ImageView宽高?
- Glide 在listview中如何加载图片的
Glide加载图片的整体流程
Glide 使用很简单:
Glide.with(Context).load(url).into(View)
所以,分成三个流程进行分析:
1. with
// Glide.java
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
with()方法有很多参数,这里只看Activity作为参数的情况。
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
return Glide.get(context).getRequestManagerRetriever();
}
RequestManagerRetriever 是检索者的意思,负责创建RequestManager,或者从Activity或Fragment找出RequestManager
Glide是一个单例,在创建的时候生成很多配置对象,比如RequestManagerRetriever 、缓存对象等。
RequestManager 顾名思义,就是负责管理Request 请求,而且会跟着Fragment或Activity的生命周期
看一下 RequestManager 是怎么生成的
// RequestManagerRetriever
@NonNull
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);
}
get()方法根据context是不同的实例,根据Context的类型是Application和非Application来获取不同的RequestManager;
这里我们只看context是Activity的情况
// RequestManagerRetriever
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
// 如果非UI线程,走Application的ReqeustManager
return get(activity.getApplicationContext());
} else {
android.app.FragmentManager fm = activity.getFragmentManager();
// 获取FragmentManager 后续可以创建存储Fragment
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
// 内部创建一个Fragment,构造RequestManager
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;
}
简述一下上面的RequestManager流程,主要目的是获取对应的FragmentManager,创建一个透明的Fragment;
getRequestManagerFragment()负责获取或者创建 SupportRequestManagerFragment(其实就是Fragment),不继续深究了,以流程为主;
这样 RequestManager自然就有了生命周期了的通知。
小结:
with() 目的是创建一个RequestManager
2.load()
RequestManager的 load() 方法返回的是一个ReqeustBuilder,分明是一个建造者模式,ReqeustBuilder负责创建Request;
RequestManager的大部分方法就是负责链式配置ReqeustBuilder的各种属性。
我们看一个最常用的load(),参数是一个String类型的URL
// RequestManager
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
看起来as()方法就是构造一个RequestBuilder,其中resourceClass表示资源类型,可以是Drawable、Bitmap、还有GifDrawable;
看完了asDrawable()构建好了RequestBuilder,最后看下ReqeustBuilder的load():
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
load(String )方法也是很简单的,大致上就是配置一下model 模型参数,表示我要加载的是什么model 模型参数,可以是uri、bitmap等等。
isModelSet是标记位,表示已设定好模型参数。
小结:
load() 目的是创建一个RequestBuilder,并配置各类参数
3. into()
以上构建好了RequestBuilder,来到最后一步RequestBuilder的into()方法,这是最重要也是最复杂的一步。
忍着头皮继续往下看吧。
这里只挑选into(ImageView)的方法
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
// 判断是否主线程
Util.assertMainThread();
Preconditions.checkNotNull(view);
// 使用默认的 RequestOptions 配置
RequestOptions requestOptions = this.requestOptions;
// 判断是否需要自定义 transform 转换裁剪
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// 根据scale type设置裁剪策略
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.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
上面出现了一个RequestOptions,它保存着所有requestManager的配置,包括缓存策略、裁剪大小、动画效果、优先级等等。在用于创建reqeust的时候,配置reqeust的属性。
into(ImageView view) 根据scale type设定了一个对应的裁剪策略。
接着调用了glideContext.buildImageViewTarget(view, transcodeClass)创建一个ViewTarget。
最后重载的方法into()
我们先说下ViewTarget,它集成自BaseTarget,相当于是Glide处理完结果的回调,负责处理最后的结果,并且带有生命周期。
Glide提供了各种各样的Target:
ImageViewTargetFactory 负责创建ViewTarget:
public class ImageViewTargetFactory {
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)");
}
}
}
假定我们传的是Bitmap,返回的是一个BitmapImageViewTarget,我们在最后拿到结果的时候再回来分析BitmapImageViewTarget是如何处理的。
创建完了BitmapImageViewTarget,往下看重载的方法into(),基本是此次分析的重点流程:
// RequestBuilder.java
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
// model在RequestBuilder的load()设置过了
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
// 1. 创建一个Request
Request request = buildRequest(target, targetListener, options);
// 2. 获取target之前的request
Request previous = target.getRequest();
// 3. 判断是否要开始这一次的请求
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// 取消请求
request.recycle();
if (!Preconditions.checkNotNull(previous).isRunning()) {
// 开始之前的请求
previous.begin();
}
return target;
}
// 4.先清除本次的target,再track开始此次请求
requestManager.clear(target);
target.setRequest(request);
// 启动、追踪
requestManager.track(target, request);
return target;
}
1. buildRequest() 会创建一个Request请求,内部会根据情况创建 三种请求:
-
ErrorRequestCoordinator 错误图片+目标图片联合请求(请求出错时,如有配置,则启动该请求)
-
ThumbnailRequestCoordinator 缩略图+目标图片联合请求(小图请求较快显示。如有配置,在请求开始时启动)
-
SingleRequest 目标图片请求(目标图片请求,在请求开始启动)
我们假定返回的SingleRequest请求
2. target.getRequest() 获取Target之前的请求
比如一个ImageView之前可能有一个Request正在请求或者执行完成。
这里我们看ViewTarget.getRequest()方法,个人觉得很有意思。
// ViewTarget.java
public Request getRequest() {
Object tag = getTag();
Request request = null;
if (tag != null) {
if (tag instanceof Request) {
request = (Request) tag;
} else {
throw new IllegalArgumentException(
"You must not call setTag() on a view Glide is targeting");
}
}
return request;
}
public void setRequest(@Nullable Request request) {
setTag(request);
}
哈哈,原来Glide每次将Request都放进了View.setTag(Request)中,这样完成结果或者需要Request就可以从Tag中取出,好机智。
3. 判断此次请求是否还要继续
request.isEquivalentTo(previous)
用来判断是否是同样的请求
isSkipMemoryCacheWithCompletePreviousRequest(options, previous))
表示之前的请求已经完成并且这次的请求不缓存
4. requestManager.track(target, request) 开始此次真正的请求
// RequestManager
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
TargetTracker内部收录着所有的target,负责在收取到生命周期的时候通知所有的target,这里相当于target注册了成为观察者。
RequestTracker管理控制所有的request,负责启动、停止、管理所有的request。
// RequestTracker
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
request.clear();
pendingRequests.add(request);
}
}
上面假定了Request 是SingleRequest,直接看SingleRequest的begin()启动方法
public void begin() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
// model是在load()方法设定的,前面假定是String url
// overrideWidth, overrideHeight是override()外部设定的裁剪的大小
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
// sh
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
// 重复请求
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// 重复请求已完成
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// 已经拿到了size,可以开始异步请求
onSizeReady(overrideWidth, overrideHeight);
} else {
// 从Target中获取目标的size,至于怎么保证拿到看后续问题,最后会走到onSizeReady()
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
// onSizeReady是异步的,所以会调用target.onLoadedStarted(),显示占位符
target.onLoadStarted(getPlaceholderDrawable());
}
}
上面的注释已经把流程标注得很清晰,最后看一个onSizeReady()方法
//SingleReqeust
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled(); // 判断是否已经被释放
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier(); // 图像的尺寸系数(0 - 1)之间,用在缩略图
this.width = maybeApplySizeMultiplier(width, sizeMultiplier); // 根据尺寸系数算高度和宽度
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
loadStatus = engine.load( // 调用Engine加载
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this);
if (status != Status.RUNNING) {
loadStatus = null;
}
}
Engine内部包含了内存缓存和磁盘缓存,和怎么加载资源,这个在后续的缓存池模块继续分析;
最后会到Target的onResourceReady()成功回调
上面我们假定了Traget是 BitmapImageViewTarget,它继承自ImageViewTraget
//ImageViewTraget
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) {
setResource(resource); // setResource是抽象方法
maybeUpdateAnimatable(resource);
}
// BitmapImageViewTarget
protected void setResource(Bitmap resource) {
// View 就是最初的into中的ImageView
view.setImageBitmap(resource);
}
小结
into() 方法复杂无比,包括生命周期管理、缓存策略、裁剪、动画效果等等。
Glide.with(Conext).load(url).into(Image)
以上,基本分析完成了Glide最简单的链式调用的流程,这次尽量的省略大片大片的源码贴出,仅对整体流程加以把控。
后续补上流程图,并遗留下几个问题还没解答。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6285.html