目录结构
- 一、简单介绍
- 二、with(context)
- 三、load(url)
- 四、into(view)
- 五、结束语
一、简单介绍
Glide
是纯Java写的Android端开源图片加载库,能够帮助我们下载、缓存、展示多种格式图片,也包括GIF格式。并且用法也及其简单,只需在Gradle中添加依赖后,再在代码中调用以下代码便可完成图片展示。
Glide.with(context).load(url).into(view);
这行代码看似简单,但是却包含了诸多复杂的处理逻辑。秉持“知其然也要知其所以然”之精神,下面我就从源码入手一探究竟,揭开Glide
神秘面纱。
二、with(context)
with(xx)
函数是Glide.java
类的静态方法,在该类中有五个重载函数,接收的参数类型有Context
、Activity
、FragmentActivity
、Fragment
和View
。
// Glide.java
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getContext()).get(fragment);
}
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
该函数创建了Glide
实例并初始化了一些基本参数,然后创建了一个RequestManager
对象并返回。总共有5个场景,这里就先选取参数为Context
类型情形进行分析。
// Glide.java
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
可以看到该函数首先调用了getRetriever(context)
获取到了RequestManagerRetriever
对象。在创建该对象之前首先通过Glide.java
中的get
方法获得了Glide
实例(Glide是一个单例),同时读取AppGlideModule
和AndroidManifest.xml
的配置。
// Glide.java
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Glide.get(context)获取Glide实例
return Glide.get(context).getRequestManagerRetriever();
}
public static Glide get(@NonNull Context context) {
if (glide == null) {
// 加载AppGlideModule
GeneratedAppGlideModule annotationGeneratedModule =
getAnnotationGeneratedGlideModules(context.getApplicationContext());
synchronized (Glide.class) {
if (glide == null) {
// 加载Mainfest配置、注册模块回调
// 这一步执行了 Glide.build()方法构造Glide实例。build方法下面会讲到
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
获取到Glide
实例后,紧接着调用getRequestManagerRetriever
方法返回了上一步已经初始化好的RequestManagerRetriever
对象。
// Glide.java
public RequestManagerRetriever getRequestManagerRetriever() {
return requestManagerRetriever;
}
接着再看一看RequestManagerRetriever
是如何被初始化的,以及初始化过程中都干了哪些事。首先贴源码看看Glide.build
方法内部具体实现(该方法在上述checkAndInitializeGlide()
函数中被调用):
// GlideBuilder.java
Glide build(@NonNull Context context) {
// 分配线程池、配置缓存策略
sourceExecutor = GlideExecutor.newSourceExecutor();
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
animationExecutor = GlideExecutor.newAnimationExecutor();
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
// 监听网络变化
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
// engine是负责执行加载任务的
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
if (defaultRequestListeners == null) {
defaultRequestListeners = Collections.emptyList();
} else {
defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
}
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
isLoggingRequestOriginsEnabled,
isImageDecoderEnabledForBitmaps);
}
通过源码我们可以了解到在执行Glide.get()
方法时就已经分配好了资源加载、缓存线程池、配置好了缓存策略,这里的engine
专门负责加载、解码资源(内部逻辑后面再讲),ConnectivityMonitor
注册了网络状态监听器,当网络断开时暂停请求网络资源,重连后继续请求资源。回归主题,注意到RequestManagerRetriever
是原来是通过RequestManagerFactory
工厂类构造的。进入到RequestManagerFactory.java
类中,可以看到get
方法获取到了相应的RequestManager
对象。从这里我们可以发现,无论哪种情况,当App进入后台后会导致页面不可见,此时RequestManager绑定到了ApplicationContext,与App的生命周期一致,因此在RequestManager.java
类中也实现了生命周期相关的回调函数。
// RequestManagerRetriever.java
// get有好几个重载方法,这里仅选取context参数进行分析
public RequestManager get(@NonNull Context context) {
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
&& ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
至此,执行完Glide.with(context)
后我们拿到了一个对应的RequestManager
对象,接下来就执行下一个任务load(url)
。
三、load(url)
上一步已经拿到了RequestManager
,紧接着调用load
方法开始执行下一步操作,同样先看看load方法的实现。
// RequestManager.java
public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) {
return asDrawable().load(bitmap);
}
public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {
return asDrawable().load(drawable);
}
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> load(@Nullable Uri uri) {
return asDrawable().load(uri);
}
public RequestBuilder<Drawable> load(@Nullable File file) {
return asDrawable().load(file);
}
public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
return asDrawable().load(resourceId);
}
public RequestBuilder<Drawable> load(@Nullable URL url) {
return asDrawable().load(url);
}
public RequestBuilder<Drawable> load(@Nullable byte[] model) {
return asDrawable().load(model);
}
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
load()
同样有多个重载函数,传入的参数可以是图片对象Bitmap
、Drawable
、本地资源Uri
、在线资源路径Url
、文件对象File
、assets资源的id
,这里我们只看参数为Url
的情形。
asDrawable().load(url)
返回了一个RequestBuilder
对象,首先看看asDrawable
方法干了什么。
// RequestManager.java
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
asDrawable
方法创建了RequestBuilder
对象,然后调用RequestBuilder.java
中的load
方法,这一步做的事比较少,没什么好讲的了。
// RequestBuilder.java
// 传入的String类型的url将会被作为缓存的key
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
函数主要工作就是根据传入的资源类型,构造了一个相应的RequestBuilder
对象。至此一切准备工作准备就绪,接下来就是最为重要的一步了-加载、展示文件,让我们来着看into(view)
方法如何完成这些任务。
四、into(view)
上一步最终拿到的是对应类型RequestBuilder
实例,那么就看看该类里into
方法的具体实现。同样into方法有into(@NonNull Y target)
和into(@NonNull ImageView )
两个重载函数(这两个函数最终都会走到同一个函数中),由于调用into
方法时我们传入的参数是ImageView类型的,所以这里就以后者为例进行分析。
// RequestBuilder.java
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
BaseRequestOptions<?> requestOptions = this;
// View's scale type.
// 处理图片缩放,根据缩放类型来初始化对应的requestOptions对象
......
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor() // 运行在主线程的handler
);
}
上面代码段首先处理图片缩放类型(裁剪、对齐方式等),并将生成的相关参数放入了requestOptions
对象中,然后再将其作为参数传给了RequestBuilder.java
类私有方法into
。该方法定义的四个参数分别为:1、viewTarget,2、target回调监听器,3、请求参数,4、主线程的回调函数。
显然外部传入ImageView
对象最终被转换成了ViewTarget
对象,转换函数便是glideContext.buildImageViewTarget(view, transcodeClass)
。
// GlideContext.java
public <X> ViewTarget<ImageView, X> buildImageViewTarget(@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
ViewTarget
又是由ImageViewTargetFactory
工厂方法生成,接着再看buildTarget
方法是如何生成ViewTarget
对象。
// imageViewTargetFactory.java
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)");
}
}
}
可以看到无论传入参数是何种类型,最终都会转换成两种类型的ViewTarget :1、BitmapImageViewTarget
;2、DrawableImageViewTarget
。这里如何选择取决于asBitmap()
、asGif()
和asDrawable()
函数是否被调用,默认是Bitmap
类型,所以这里默认返回的是BitmapImageViewTarget
。
// BitmapImageViewTarget.java
public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {
public BitmapImageViewTarget(ImageView view) {
super(view);
}
@Override
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource); // 显示图片
}
}
至此ViewTarget
创建完毕,我们再回到RequestBuilder.java
私有into
方法。代码重新贴一遍,省得再往前翻了。
// RequestBuilder.java`
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
// 注释1:创建request
Request request = buildRequest(target, targetListener, options, callbackExecutor);
// 获取前一个reqeust请求对象
Request previous = target.getRequest();
// 与上一个请求相同 并且 上一个请求已完成
if (request.isEquivalentTo(previous)&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// 上一个请求已完成,那么重新启动它
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
// 与上一个请求不同,则清除掉上一个,再将加入新请求
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
顺着代码次序,来看看这个方法每一步都干了什么:
- 第一步:首先执行
buildRequest
方法创建一个新的Request
请求req1
; - 第二步:获取当前
ViewTarget
上正在进行中的Request
请求req2
; - 第三步:判断新建的请求
req1
与已有的请求req2
是否相同,如果相同则判断是否跳过req2
请求的缓存,两个条件都满足则开始执行begin()
方法开始请求资源并停止往下执行,条件都不满足则继续执行第四步; - 第四步:给
ViewTarget
设置最新的请求req1
,然后执行track
方法追踪req1
。
总结一下,执行into(view)
方法首先获取到了Request
请求,然后开始执行Request
。如果是复用的Request
则直接执行begin()
,否则执行track(target, request)
,但最终仍然会执行begin()
。
// ReqeustManager.java
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
// 与lifecycle绑定
targetTracker.track(target);
// 启动reqeust
requestTracker.runRequest(request);
}
// RequestTracker.java
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin(); // 立即开始加载
} else {
//防止从以前的请求中加载任何位图,释放该请求所拥有的任何资源,显示当前占位符(如果提供了该占位符),并将该请求标记为已取消。
// request.java( Interface )
request.clear();
pendingRequests.add(request); // 加入队列等待执行
}
}
我们首先看看track方法的源码,先是执行targetTracker.track(target)
监听ViewTarget
的请求,然后runRequest
开始执行。由于最终都是通过begin()
方法开始请求,所以我们先来看看begin()
方法的具体实现。
Request
类是interface
类型,begin()
它的抽象方法,所以我们要想弄清楚begin()
的具体实现,那就要先找到Request
的实现类,从buildRequest(xx)
方法入手,同样先贴出源码:
// RequestBuilder.java
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
return buildRequestRecursive(
/*requestLock=*/ new Object(),
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
private Request buildRequestRecursive(
Object requestLock,
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
ErrorRequestCoordinator errorRequestCoordinator = null;
// 请求出错了
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
// 无法确认完成请求和缩略图请求哪个先完成,所以当缩略图比完成请求后完成时就不再显示缩略图
Request mainRequest =
buildThumbnailRequestRecursive(
requestLock,
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
// 请求成功了,直接返回缩略图Request
if (errorRequestCoordinator == null) {
return mainRequest;
}
// ...
Request errorRequest =
errorBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
// 同时返回缩略图请求和错误请求
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
显然代码里的mainRequest
就是我们要找的Request
了,它是由buildThumbnailRequestRecursive
方法返回的,深入其内部我们发现Request
最终其实是由SingleRequest.obtain
方法产生,也就是说我们最终拿到的Request
其实就是SingleReqeust
类的一个实例。这里过程比较简单,代码就不贴出来了。我们直接去SingleReqeust
类里面 看看begin
方法如何实现的.
// SingleReqeust.java
public void begin() {
if (status == Status.COMPLETE) {
// 资源已下载,直接回调
// 执行动画
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
// 计算尺寸
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
// 开始加载
// 设置占位度
target.onLoadStarted(getPlaceholderDrawable());
}
}
进入begin
方法后首先判断如果资源已经过加载好了则直接回调onResourceReady
显示图片并缓存,否则测量出图片尺寸后再开始加载图片(onSizeReady()中执行加载任务)并同时显示占位图。
1、
overrideWith
、overrideHeight
通过override(width, height)
设置:
Glide.with(mContext).load(url).override(75, 75).into(imageView);2、占位图是用户调用
placeholder(resId)
设置:
Glide.with(mContext).load(url).placeholder(resId).into(imageView);
接着再看onSizeReady()
测量完图片尺寸后如何加载图片的:
// SingleRequest.java
@Override
public void onSizeReady(int width, int height) {
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
// 获取图片尺寸
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
// 开始加载任务
loadStatus =
engine.load(
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,
callbackExecutor);
}
从上述源码可以看到真正的下载任务是在Engine
类的load
方法中实现的,其中也涉及到了图片缓存逻辑,很复杂。
五、结束语
至此,Glide初始化、显示占位图、图片封面的整个业务流程都走完了。接着往下走就进入资源加载逻辑和缓存策略了,由于这块内容逻辑最复杂且最重要,所以就不打算放到这篇文章来讲了,计划后续安排时间再次深入学习并做专题分享。
再说说读完源码之后的几点个人感悟:
1、一个好的框架在结构设计上一定是非常清晰明确的;
在源码中有大量的
Interface
,阅读时往往需要去寻找它的定义和具体实现,实际上很快就能找到对应的点,不需要耗费太多的时间。这主要是得益于源码清晰的结构设计,通过对应的包名和一看就懂的字段名想找到对应的点简直太容易了。主要业务代码的聚合和分离拿捏地很准,聚而不乱、分而不散,读起来既省时又省心让人读而不倦。
2、学习设计模式不仅有助于阅读、理解源码,也是自己写出优秀代码的必备基础;
源码中一眼就能看出来的设计模式有
单例模式
、工厂方法
,再加上诸多其他不是很容易看出来的设计模式共同构筑了代码的骨架。所以设计模式是阅读源码的必备基础,否则即使能读懂源码也很难读懂结构设计的核心思想,更别提自己能写出多么优秀的代码了。
3、读源码是程序员提升自我修养的不二法门。
深知优秀的程序员不是凭空冒出来的,而是要“站在巨人的肩膀上”并经过不断地学习实践再发挥,一步步成长起来的。那么程序员要找的“巨人”是谁呢?我觉得那就是大牛写出来的优秀源码。读源码既是一个学习、理解、接受他人优秀思想的重要途径,也是自我审视、提升最高效的方式。
网友评论