Android | 《看完不忘系列》之Glide
《看完不忘系列》将以从树干到细枝
的思路来分析一些技术框架,本文是开篇文章,将对开源项目Glide
图片加载库进行介绍。如果老铁们看完还是忘了,就 回来揍我一顿 点赞收藏加关注
,多看两遍~
概览
基于Glide
最新版本4.11.0
,未迁AndroidX的项目只能使用4.9.0
,简单使用:
引入依赖,app/build.gradle:
implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
一句代码,完成图片加载:
Glide.with(this) //指定上下文,可以是app、activity、fragment .load(url) //网络图片地址 .into(img); //用于展示的imageView
用起来简洁优雅,然后我们先大致预览下Glide
的一些职能,
树干:核心流程
以Glide.with(this).load(url).into(img)
为起点,拆成with购车
、load上牌
、into发车
三个环节来分析。
with:购车
简单来说,with的功能就是根据传入的上下文context来获取图片请求管理器RequestManager
,他用来管理和启动图片请求,
context可以传入app、activity、fragment,这决定了图片请求的生命周期。通常是使用粒度较细的context,即使用当前页面的context而不是全局的app。这样做的好处是,打开一个页面开启图片加载,然后退出页面,图片请求就会跟随页面销毁而被取消,而不是继续加载而浪费资源。
当context是app时,通过RequestManagerRetriever
获得的RequestManager
是一个全局单例,这类图片请求的生命周期将会跟随整个app,
class RequestManagerRetriever implements Handler.Callback { volatile RequestManager applicationManager; RequestManager getApplicationManager(Context context) { //双重检查锁,获取单例的RequestManager if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build(glide,new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(),context.getApplicationContext()); } } } //返回应用级别的RequestManager单例 return applicationManager; } }
当context是Activity时,创建一个SupportRequestManagerFragment
(无界面的空fragment)添加到Activity,从而感知Activity的生命周期,同时创建RequestManager
给空fragment持有,
class RequestManagerRetriever implements Handler.Callback { RequestManager supportFragmentGet(Context context,FragmentManager fm, Fragment parentHint,boolean isParentVisible) { //获取空fragment,无则创建 SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); //如果空fragment没有RequestManager,就创建一个 requestManager = factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); //让空fragment持有RequestManager current.setRequestManager(requestManager); } //返回页面级别的RequestManager return requestManager; } }
然后看到getSupportRequestManagerFragment
方法,
class RequestManagerRetriever implements Handler.Callback { Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments = new HashMap<>(); SupportRequestManagerFragment getSupportRequestManagerFragment( final FragmentManager fm, Fragment parentHint, boolean isParentVisible) { //通过tag找到Activity中的空fragment SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) { //findFragmentByTag没找到空fragment,有可能是延迟问题?再从Map中找一下 current = pendingSupportRequestManagerFragments.get(fm); if (current == null) { //确实没有空fragment,就创建一个 current = new SupportRequestManagerFragment(); //... //缓存进Map pendingSupportRequestManagerFragments.put(fm, current); //空fragment添加到Activity,使其能感知Activity的生命周期 fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); //... } } return current; } }
综上,通过with操作,
当context是app时,得到应用级别RequestManager
全局单例;
当context是Activity时,每个页面都会被添加一个空fragment,由空fragment持有页面级别RequestManager
。
注意:如果with发生在子线程,不管context是谁,都返回
应用级别RequestManager
单例。发散:添加空fragment来感知页面生命周期的思想,在Lifecycle的实现中也可以看到,见ReportFragment的
injectIfNeededIn
方法。(不过这个方法在Lifecycle的2.2.0版本中有所改动,Android 10开始的设备改成了使用Application.ActivityLifecycleCallbacks来感知,感兴趣可以康康)
至此,我们根据存款context
买到了心仪的车RequestManager
,下面开始上牌~
load:上牌
load方法得到了一个RequestBuilder
图片请求构建器,见名知意猜一下,是用来创建图片请求的,
class RequestManager implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>> { RequestBuilder<Drawable> load(String string) { return asDrawable().load(string); } RequestBuilder<Drawable> asDrawable() { //需要加载的类型为Drawable return as(Drawable.class); } <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) { //创建一个请求构建器 return new RequestBuilder<>(glide, this, resourceClass, context); } }
然后跟进asDrawable().load(string)
,
class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>> implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> { RequestBuilder<TranscodeType> load(String string) { return loadGeneric(string); } RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { //只是简单地赋值 this.model = model; isModelSet = true; return this; } }
到这里,我们就完成了上牌,得到了一个RequestBuilder
图片请求构建器。没错,上牌就这么简单,毕竟摇号已经够艰难了对吧?
into:发车
阶段一
来到into了,
class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>> implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> { ViewTarget<ImageView, TranscodeType> into(ImageView view) { //... BaseRequestOptions<?> requestOptions = this; if (!requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() != null) { //根据ImageView的ScaleType,来配置参数 switch (view.getScaleType()) { case CENTER_CROP: requestOptions = requestOptions.clone().optionalCenterCrop(); break; case CENTER_INSIDE: requestOptions = requestOptions.clone().optionalCenterInside(); break; //... } } return into( //前面提到,Target是展示图片的载体,这里他封装了ImageView glideContext.buildImageViewTarget(view, transcodeClass),null, requestOptions,Executors.mainThreadExecutor()); } }
继续跟进into重载方法,
class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>> implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> { <Y extends Target<TranscodeType>> Y into(Y target,RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options,Executor callbackExecutor) { //... //创建图片请求 Request request = buildRequest(target, targetListener, options, callbackExecutor); //获取Target载体已有的请求 Request previous = target.getRequest(); //如果两个请求等效,并且xxx(先忽略) if (request.isEquivalentTo(previous) && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { if (!Preconditions.checkNotNull(previous).isRunning()) { //启动异步请求 previous.begin(); } return target; } requestManager.clear(target); //图片载体绑定图片请求,即imageView setTag为request target.setRequest(request); //启动异步请求 requestManager.track(target, request); return target; } }
跟进requestManager.track(target, request)
,
//RequestManager.java void track(Target<?> target,Request request) { targetTracker.track(target); requestTracker.runRequest(request); } //RequestTracker.java void runRequest(Request request) { requests.add(request); if (!isPaused) { //开启图片请求 request.begin(); } else { request.clear(); //如果处于暂停状态,就把请求存起来,晚些处理 pendingRequests.add(request); } }
到这里,就启动了图片请求,
阶段二
那么,接下来重点关注的就是request.begin()
了,
class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback { void begin() { synchronized (requestLock) { //... if (Util.isValidDimensions(overrideWidth, overrideHeight)) { //如果已经有了明确的尺寸,开始加载 onSizeReady(overrideWidth, overrideHeight); } else { //没有的话先去获取尺寸,最终还是走onSizeReady target.getSize(this); } //... } } void onSizeReady(int width, int height) { synchronized (requestLock) { //... //engine.load,传了很多参数 loadStatus = engine.load(glideContext,model, requestOptions.getSignature(), this.width,this.height, requestOptions.getResourceClass(), transcodeClass,priority, requestOptions.getDiskCacheStrategy(), //... this,callbackExecutor); //... } } }
跟进engine.load
,
class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { <R> LoadStatus load( GlideContext glideContext, //... Executor callbackExecutor) { //... EngineResource<?> memoryResource; synchronized (this) { //从内存加载 memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); if (memoryResource == null) { //如果内存里没有缓存,则加载 return waitForExistingOrStartNewJob( glideContext, model, //... key, startTime); } } cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE); return null; } <R> LoadStatus waitForExistingOrStartNewJob(...) { //... EngineJob<R> engineJob =engineJobFactory.build(...); DecodeJob<R> decodeJob =decodeJobFactory.build(...); jobs.put(key, engineJob); //添加回调,这个cb就是SingleRequest自己,todo1 engineJob.addCallback(cb, callbackExecutor); //engineJob开启decodeJob engineJob.start(decodeJob); return new LoadStatus(cb, engineJob); } }
DecodeJob
是一个Runable,看看他的run方法,调用链如下:
DecodeJob.run -> DecodeJob.runWrapped -> DecodeJob.runGenerators ->
SourceGenerator.startNext -> SourceGenerator.startNextLoad ->
MultiModelLoader#MultiFetcher.loadData ->
HttpUrlFetcher.loadData
来到HttpUrlFetcher
,
class HttpUrlFetcher implements DataFetcher<InputStream> { void loadData(Priority priority, DataCallback<? super InputStream> callback) { try { //获取输入流,没有引入okhttp,则使用HttpURLConnection InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); //回调出去 callback.onDataReady(result); } catch (IOException e) { callback.onLoadFailed(e); } finally { } } }
到这里,网络请求就完成了,下面看看图片是怎么设置上去的,前边留了个todo1,
//添加回调,这个cb就是SingleRequest自己,todo1
callback就是SingleRequest
,被回调前有一些对象包装、解码操作,暂不深究,来到SingleRequest
,
class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback { void onResourceReady(Resource<?> resource, DataSource dataSource) { Resource<?> toRelease = null; try { synchronized (requestLock) { //... Object received = resource.get(); //... //这里 onResourceReady((Resource<R>) resource, (R) received, dataSource); } } finally { if (toRelease != null) { engine.release(toRelease); } } } void onResourceReady(Resource<R> resource, R result, DataSource dataSource) { //... try { //... //这里,回调给Target载体 target.onResourceReady(result, animation); } finally { } notifyLoadSuccess(); } }
跟进target.onResourceReady
,最终来到DrawableImageViewTarget
,
class DrawableImageViewTarget extends ImageViewTarget<Drawable> { void setResource(Drawable resource) { //ImageView设置图片 view.setImageDrawable(resource); } }
结合阶段一和二,
终于,汽车发动起来了~
把with购车
、load上牌
、into发车
三个环节汇总起来,
可以看到,整个图片加载过程,就是with得到RequestManager
,load得到RequestBuilder
,然后into开启加载:
创建Request
、开启Engine
、运行DecodeJob
线程、HttpUrlFetcher
加载网络数据、回调给载体Target
、载体为ImageView
设置展示图片。
细枝:补充
线程切换
在子线程下载完图片后,如何回调主线程设置图片?在Executors
类里,
private static final Executor MAIN_THREAD_EXECUTOR = new Executor() { //主线程Handler private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable command) { //切回主线程 handler.post(command); } };
调用栈如下:
线程池
GlideBuilder
类里会初始化一些线程池:
Glide build(Context context) { private GlideExecutor sourceExecutor; //加载图片 private GlideExecutor diskCacheExecutor; //管理磁盘缓存 private GlideExecutor animationExecutor; //管理动画 }
缓存
有内存缓存和磁盘缓存,在Engine.load
时会去取,篇幅原因后面单独开篇来写。
webp动图
Fresco
支持解析webp动图,Glide
不支持,不过已经有了开源的方案,见GitHub - GlideWebpDecoder。
选型
Fresco
和Glide
怎么选?
Fresco
具有一定侵入性,需要继承SimpleDraweeView
;
Fresco
调用繁琐,没有Glide
的链式调用优雅,当然这个可以包一层来解决;
Fresco
在5.0以下的系统进行了内存优化(Ashmem区),这个优势在当下的环境已经不值一提,因为这些系统占比已经非常低了,一些App的minSDK都已经设置成21了。
所以,更推荐使用Glide
(个人拙见,仅供参考)
尾声
作为《看完不忘系列》的文章,本文删减了很多源码,重点在于理清Glide
图片加载流程,大家看的时候最好能跟着思路去阅读源码~然后,Glide
还有解码、缓存的流程没有分析,后面会单独开篇来写。
参考资料
- 掘金 - 面试官:简历上最好不要写Glide,不是问源码那么简单
- 掘金 - 锦囊篇|一文摸懂Glide
- 掘金 - Android主流三方库源码分析(三、深入理解Glide源码)
- 官方文档 & GitHub
- GitHub - GlideWebpDecoder