Android Glide浅析

目前在android项目上,图片加载库有很多选择,Glide是主流的加载库之一,作为一个被google推荐的开源库,它有着跟随页面周期、支持gif和webp、支持多种数据源等特点,并且使用起来很简单,本篇文章将会分析Glide的加载流程,但是Glide的源码比较复杂,我将从Glide的最简单使用分析Glide是如何去加载一张图片的。

(一)从Glide.with(context)入手

                                                                                      Glide.with(context).load(url).asBitmap().into(imageView);

通常,这将是最简单的使用Glide的方式,那么首先我们进入Glide这个类看看:



glide的with方法

Glide.wth(context)会返回一个RequestManager对象,我们发现Glide重载了多个with方法,返回值都是去调用RequestManagerRetriever.get()方法:


glide的get方法

RequestManagerRetriever类中会对context加以区分后走对应的get方法,首先判断如果不是主线程,则用ApplicationContext调用get方法:

通用的get方法


getApplicationManager

直接new了一个RequestManager对象,将Appcation级别的信息传递了进入,那么如果是在主线程调用,无论context是fragment还是activtiy,最后都会走fragmentGet和supportFragmentGet这两个方法:


获取fragment

这两个方法都是向当前页面添加一个fragment,一个是添加v4包的,一个是添加android.app.Fragment,我们从fragmentGet看起,首先通过getRequestManagerFragment获得了一个RequestManagerFragment对象:

配置requestManager

很简单,先从缓存里找,没有就new一个再添加上去。

回到fragmentGet,接着从改fragment中获取RequestManager,因为是才new的,所以RequestManager为空,那么接着new了RequestManagert同时将fragment的lifecycle对象传递进去,set给fragment后返回了manager。

因为Glide.wth(context)最后就是返回了一个RequestManagert对象,所以这一步到这里结束了,那添加的这个fragment有什么用呢,我们分别打开RequestManagerFragment和RequestManager看看:


RequestManagerFragment部分代码


RequestManagerFragment部分代码

可以看到RequestManagerFragment在初始化的时候创建了一个ActivityFragmentLifecycle对象,在下面三个生命周期的时候分别调用了ActivityFragmentLifecycle对应的方法。


RequestManager部分代码

在RequestManager的构造方法中,最后传进来的lifecycle分别add(this)和addListener(connectivityMonitor),所以当RequestManagerFragment对应周期调用时,RequestManager也会调用对应方法,其实光看类名我们就能明白RequestManager是对每个ImageRequest进行管理的类,那么这样一来,每个ImageRequest就和Fragment的生命周期同步,所以Glide是这样做到请求与页面周期同步的。我们再来看看具体是如何管理的以及加入的connectivityMonitor的作用:

RequestManager部分代码

RequestManager的三个方法中,最后都是调用requestTracker的方法,所以requestTracker是最后的request容器。再看看最开始的lifecycle.addListener(connectivityMonitor),它由ConnectivityMonitor connectivityMonitor = factory.build(context,   new RequestManagerConnectivityListener(requestTracker)),构造而来,也将requestTracker传递了进去:


ConnectivityMonitorFactory

加载图片自然是要联网了,进入DefaultConnectivityMonitor看看:


DefaultConnectivityMonitor简化代码
DefaultConnectivityMonitor简化代码


DefaultConnectivityMonitor简化代码

既然它被lifecycle添加进去了,那么一定有对的几个方法,这个类其实主要是注册了一个广播去监听网络状况,当网络状况变化的时候调用listener.onConnectivityChanged(isConnected),这个listener就是一开始传递进来的RequestManagerConnectivityListener了:

RequestManagerConnectivityListener

所以最终的作用其实是在网络重连的时候重启request,那么Glide.with(context)的主要流程我们就走完了。

一张图总结如下


fragment与request周期管理


(二)load()和asBitmap()

 在with()方法之后将返回RequestManager对象,那么接着我们直接去RequestManager的load(String)方法看看:


RequestManager相关方法


RequestManager相关方法

追着流程下去最后是进入了loadGeneric(Class modelClass)方法,该方法首先生成了两个ModelLoader对象,然后将这两个对象合着其他的内容传入了一个新的DrawableTypeRequest对象中,虽然最后的返回还调用了optionsApplier.apply(),但是我们知道Glide.with(context).load(url)返回的就是一个DrawableTypeRequest对象,所以我们大概可以判断最后就是返回这个新生成的DrawableTypeRequest对象了。

这里的两个ModelLoader生成过程太过复杂就不分析,我们直接跟着主流程走下去


debug

但是我们可以通过断点知道这里获得的两个对象是StreamStringLoader和FileDescriptorStringLoader,接着我们看看optionsApplier是什么:


RequestManager的构造方法

在RequestManager的构造方法中optionsApplier由new了一个新的OptionsApplier对象赋值,那么我们看看OptionsApplier的apply方法:


OptionsApplier


debug

调用options.apply之后就把传入的对象返回了,这个options我们从来没有设置过,同时在debug中它也确实为空,那么确实和我们猜测的一样,loadGeneric()中直接返回了new的DrawableTypeRequest对象,紧接着调用了它的load方法:


DrawableTypeRequest继承关系


DrawableRequestBuilder


GenericRequestBuilder

可见load方法只是对model赋值,这里传入的model就是url,最后返回本身,那么Glide.with(context).load(url)也走完了,接着我们来看DrawableRequestBuilder的asBitmap()方法


asBitmap()

生成了一个BitmapTypeRequest对象并将DrawableRequestBuilder的相关配置传入且返回,所以整个Glide.with(context).load(url).asBitmap()还是比较清楚的,最后返回了一个BitmapTypeRequest对象


(三)复杂的into()

讲完了前几个步骤现在只剩下最后的的into()方法了,目前的几个步骤只是传递了参数,但是实际的图片请求毛都没见着,所以你应该可以预计into()方法做的事很多了,into()方法是在BitmapTypeRequest父类中实现的:


BitmapTypeRequest继承关系


into()方法

我们并没有做scaleType的配置,接着看glide.buildImageViewTarget(view, transcodeClass)


buildImageViewTarget


Glide类

在Glide类中很清楚发现接着该找ImageViewTargetFactory.buildTarget()


ImageViewTargetFactory

ImageViewTargetFactory代码很简单,是作为一个工具类使用的,根据传递的class生成对应的ImageViewTarget,根据我们调用的Glide.with(context).load(url).asBitmap()其实我们可以猜测这里的clazz是Bitmap.class,我们还是去理一理怎么来的:


into()方法


GenericRequestBuilder构造方法


BitmapTypeRequest构造方法

看了构造方法我们可以发现BitmapTypeRequest确实传递了Bitmap.class进去,那么into()方法的传递参数是一个BitmapImageViewTarget对象


into()方法

在接下来into()方法中首先从target中取出之前的request,将之前的request回收掉,接着调用buildRequest(target)创建了一个新的request添加到requestTracker中,并且将target添加到lifecycle中,这列lifecycle对象就是一开始的ActivityFragmentLifecycle,那么这个request也将会跟着一开始添加的fragment生命周期运行了。在RequestTracker中,运行request其实是调用request.begin(),那么我们进入buildRequest(target)找找看生成的request这个方法是做了什么:


buildRequest相关方法

thumbnail相关是设置略缩图的,这里我们不用管,直接去看obtainRequest(target, sizeMultiplier, priority, parentCoordinator)


obtainRequest()方法


GenericRequest.obtain()方法

追踪下去可以看到最后生成的是GenericRequest对象,再接着看看begin()方法:

begin()方法

我们先看下面的代码:


begin()方法部分代码

如果是在正常状态,那么就调用target.onLoadStarted,那么我们回到BitmapImageViewTarget,onLoadStarted是在父类ImageViewTarget里重写的,我们看看干了什么:


ImageViewTarget.onLoadStarted()

所以是将我们传递给into()方法的ImageView设置了一张图片,原来我们在使用glide设置placeHolder图片就是在这个时候设置进来的了。再回到begin()方法,最上面是设置erorHolder的方法,只要model等于空,同样的设置错误站位图,这个model是什么呢,还记得我们在分析Glide.with().load()方法的源码吗:

RequestManager.load()


GenericRequestBuilder.load()

传入的url最后传递给model,而这个model最后在生成GenericRequest的时候又传递给了它:


obtainRequest()生成GenericRequest

所以加载之前的判断条件是url是不是为空,为空则为异常。

回到begin()方法,上面是异常,下面是默认展位图,那么中间就是加载图片的部分了:


begin()方法部分代码

Util.isValidDimensions判断是否设置了有效的overrideWidth和overrideHeight,我们没有设置过直接走else,target是BitmapImageViewTarget,在它的父类ViewTarget中实现了getSize()方法:

ViewTarget.getSize()


ViewTarget构造方法


SizeDetermine.getSize()

依次追下去,我们发现最后调用的SizeDetermine.getSize(),currentWidth和currentHeight是展示加载的图片的ImageView的有效宽高,通常都可以获取到,那么我们直接去看cb.onSizeReady(currentWidth, currentHeight),这里传进来的cb也就是GenericRequest本身了。

GenericRequest.onSizeReady()

ImageView的宽高进来之后,计算了一个设置的宽高,因为默认sizeMultiplier是为1的,如果没有设置过sizeMultiplier默认设置宽高即为ImageView的宽高,之后的modelLoader和dataFetcher又得绕回去了,我们回去看看DrawableTypeRequest的构造方法:


DrawableTypeRequest相关方法

在DrawableTypeRequest的构造方法中调用了super构造方法,其中第三个参数传递了一个LoadProvider,这个provider就是之后传递到GenericRequest的loadProvider对象,buildProvider()方法一起贴出来了,返回了一个FixedLoadProvider对象,我们进入FixedLoadProvider发现getModelLoader返回的就是构造时传入的ImageVideoModelLoader对象,并且getTranscoder返回的也是构造时传递的transcoder,

FixedLoadProvider部分代码

我们跟着glide.buildTranscoder(resourceClass, transcodedClass)的流程,看看transcoder是什么:


glide.buildTranscoder()


transcoderRegistry.get()

我们知道传递进来的decodedClass和transcodedClass分别是GifBitmapWrapper.class和GlideDrawable.class,那么这里最后会由factories.get(GET_KEY)返回结果,而在Glide类的构造方法中有一句这样的初始化代码:


Glide构造函数部分代码

在这里注册了GifBitmapWrapperDrawableTranscoder,那么最后返回就是GifBitmapWrapperDrawableTranscoder了,

回到onSizeReady()方法,这些参数和其他一些配置都一起传递给了engine.load(),那我们找下engine是如何生成的:

GenericRequestBuilder.obtainRequest()

GenericRequestBuilder.obtainRequest()在创建GenericRequest的时候获取了glide的engine对象传递了进去,而glide初始化是通过GlideBuilder完成的:


GlideBuilder.createGlide()

在创建engine的时候,将cache和线程相关传递进去了,那么我们可以确定,图片的加载最后是交给engine处理了:


Engine.load()方法

在这个方法中首先根据配置生成了对应的key,之后是获取缓存的操作,我们先不看,因为还不知道是如何设置缓存的,我们直接看最下面的几段代码,首先根据key创建了一个EngineJob对象,其次根据配置选项创建了一个DecodeJob对象,随后将他们传入了一个EngineRunnable对象中,那么加载图片就是在这个runnable中执行了,调用了engineJob.start(runnable)之后就开始加载图片:

EngineRunnable.run()


EngineRunnable.decode()

加载图片是通过decode()方法进行了,这里判断了是否要走缓存,我们直接不看缓存直接走decodeFromSource(),虽然EngineRunnable默认是走一次缓存的,但是第一次加载是没有缓存的,最后还是走decodeFromSource()

EngineRunnable.decodeFromSource()


DecodeJob.decodeSource()

在DecodeJob中decodeSource()首先通过fetcher.loadData()获取data,这个fetcher是啥呢,我们重新回顾下DecodeJob的生成:

GenericRequest.onSizeReady()部分代码


Engine.load()部分方法

fetcher是这么传进来的,loadProvider.getModelLoader()返回的是ImageVideoModelLoader:


ImageVideoModelLoader.getResourceFetcher()

所以这里最后返回了一个ImageVideoModelLoader对象


ImageVideoFetcher.loadData()

随后进入了loadData()方法,其实在这里fileDescriptor获取为空,这是因为最开始初始化的原因,也是我一开始说的StreamStringLoader和FileDescriptorStringLoader生成太过复杂,我们只走主流程,streamFethcer是个OkHttpStreamFetcher对象,那么我们看看它的loadData()方法:


OkHttpStreamFetcher.loadData()

从OkHttpStreamFetcher名字可以看出,我所使用的这个版本的Glide是使用okhttp去加载图片,确实loadData里也是使用okhttp去加载的

OkHttpUrlLoader$Factory.getInternalClient()

在OkHttpUrlLoader$Factory中确实也初始化了OkHttpClient

回到ImageVideoModelLoader,fetcher.loadData()返回的内容会封装在ImageVideoWrapper对象中回到DecodeJob返回,接着会把他传递到decodeFromSourceData()处理:

decodeFromSourceData()

我们没有做过磁盘缓存的配置,直接进入else判断,首先我们看loadProvider.getSourceDecoder()是什么:


loadProvider.getSourceDecoder()

这里的dataLoadProvider是初始化的时候传入的:


FixedLoadProvider初始化


DataLoadProviderRegistry生成DataLoadProvider
Glide初始化

由上面的步骤可知,根据FixedLoadProvider初始化出入的class,生成的是ImageVideoGifDrawableLoadProvider对象,而ImageVideoGifDrawableLoadProvider.getSourceDecoder()则返回GifBitmapWrapperResourceDecoder:


ImageVideoGifDrawableLoadProvider初始化

那么接着看GifBitmapWrapperResourceDecoder.decode():

decode()


decode()


decodeStream()


decodeBitmapWrapper()

GifBitmapWrapperResourceDecode的流程比较清楚,由于我们设置的是bitmap,按照流程下去最后会在decodeBitmapWrapper()返回了一个GifBitmapWrapper对象,这里还需要看看bitmapDecoder.decode(toDecode, width, height),虽然后面经过的类比较多,但是其实都只提供了一个方法去实现功能,那么这里我直接断点跟着流程进去了:


ImageVideoBitmapDecoder

bitmapDecoder是一个ImageVideoBitmapDecoder对象,其中又调用了streamDecoder.decode(is, width, height):

StreamBitmapDecoder

StreamBitmapDecoder再调用了downsampler.decode(source, bitmapPool, width, height, decodeFormat):


downsampler部分代码

Downsampler类中最后生成的bitmap就是调用BitmapFactory.decodeStream()获得的,这个bitmap会被封装进Resource对象返回,最后封装成回到EngineRunnable.run()返回,并传入onLoadComplete():

EngineRunnable.run()

这个结果会回传到EngineJob通过handler回调至handleResultOnMainThread()方法:

EngineJob.handleResultOnMainThread()

在这个方法中又将结果通过ResourceCallback回传,而在Engine.load()方法中我们将之前的GenericRequest添加进cbs了,所以这里又会回到GenericRequest:

Engine.load()


GenericRequest.onResourceReady()

在onResourceReady()方法中调用了target.onResourceReady(result, animation),还记得我们在调用into(ImageView)方法时ImageView被封装进了BitmapImageViewTarget吗,这里的target就是它了,:


BitmapImageViewTarget.setResource()

终于,到这里图片被加载下来并设置上view了!

回头看看Engine.load()中之前遗留下来的的东西:


Engine.load()

这三个处理分别从cache、activeResources、jobs去取内容,我们在获取到bitmap传递内容的时候封装的Resource对象会又被封装进EngineResource在Engine之间传递,在EngineResource中有个释放资源的方法,里面的listener就是Engine:

EngineResource.release()


Engine.onResourceReleased()

也就是在释放资源的时候内存中做了缓存,而activeResources是保存当前活跃的Resource,分别会在Complete()和获取cache的时候保存Resource,Released()的时候移除Resource:

保存Resource


保存Resource


移除Resource

jobs也类似,在任务完成或者取消的时候会移除job,如果发起的任务已有就不重复发起:


移除job时机


现在看来,Glide.with(context).load(url).asBitmap().into(imageView)这一行代码居然做了这么多的操作,而且我们还只是走了主线,但是其中很多的技巧已经很值得让人学习了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容