Glide源码解析(4.X版本)

知识点汇总:

一:Glide项目概述

二:Glide加载图片的原理

三:Glide三级缓存的设计

四:Glide如何实现生命周期的监听

五:Glide是如何实现界面销毁时,界面相关请求关闭的

六:Glide与Fresco的区别

七:项目疑问汇总

八:扩展阅读


一:Glide项目概述

官方概述:Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。

        Glide支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。

        虽然Glide的主要目标是让任何形式的图片列表的滚动尽可能地变得更快、更平滑,但实际上,Glide几乎能满足你对远程图片的拉取/缩放/显示的一切需求。

API:

Glide使用简明的流式语法API,这是一个非常棒的设计,因为它允许你在大部分情况下一行代码搞定需求:

Glide.with(fragment)

    .load(url)

    .into(imageView);

性能:

Glide充分考虑了Android图片加载性能的两个关键方面:

1、图片解码速度

2、解码图片带来的资源压力

       为了让用户拥有良好的App使用体验,图片不仅要快速加载,而且还不能因为过多的主线程I/O或频繁的垃圾回收导致页面的闪烁和抖动现象,Glide使用了多个步骤来确保在Android上加载图片尽可能的快速和平滑:

1、自动、智能地下采样(downsampling)和缓存(caching),以最小化存储开销和解码次数;

2、积极的资源重用,例如字节数组和Bitmap,以最小化昂贵的垃圾回收和堆碎片影响;

3、深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,并有利于应用在必要时释放资源以避免在后台时被杀掉。


二:Glide加载图片的原理

       首先我们只需要通过很简单的代码,例如Glide.with(fragment).load(url).into(imageView),就可以很方便的实现相关图片的加载,通过相关的参数传入,Glide大概可以得到的信息如下:

1、图片的宽高

2、控件的缩放类型(scaleType)

3、当前的运行环境(Application、Activity、Fragment)

4、图片的资源类型(本地图片、远程图片等)

       在获取了相关参数的信息后,Glide如何最有效的处理该图片,并展示在相关控件上,同时,当控件是以列表的形式出现时,当用户快速滑动列表时,如何有效的处理大量的图片加载时,有效的根据用户需求处理图片(列表快速滑动可设置不加载、列表预加载等)。

       从上面的链式调用代码中,我们看到,我们最后执行into函数后,就可以把服务器的图片资源加载到控件上,那我们就具体看看该项目是如何实现的。

RequestBuilder类的函数:

备注:这里使用clone的原因是,在此方法中进行克隆,以便如果我们使用此RequestBuilder加载到视图中,然后加载到不同的目标中,我们不会保留基于前一个视图的缩放类型应用的转换。

解析:通过上面的链式调用,最终是通过在类RequestBuilder中执行into函数做图片加载的行为,链式调用中RequestBuilder对象是如何生成的,需要去看看with()与load()函数具体的实现细节,上面函数中,除了设置控件的具体实现时和判断当前请求对象是否已经加载过等操作,最终的资源加载还是执行requestManager.track()函数,下面我们具体在看看。

解析:上面的Request是接口,实现类有ErrorRequestCoordinator、SingleRequest、ThumbnailRequestCoordinator,从类名判断实现是在SingleRequest中,而在函数begin中,通过判断当前请求的状态,最终如果执行图片加载,是执行函数onSizeReady函数,下面继续看看,中间忽略部分调用代码。

Engine类的load函数:

解析:在执行图片加载任务时,首先通过查找缓存,如果缓存内存不存在,再执行资源的进一步加载,关于缓存的实现,我们会在后面解析(三级缓存),这里我们主要是查看缓存不存在时,图片是如何加载的,我们进一步看看加载的核心代码。

解析:该函数在DecodeJob类,在加载资源时,重点查看startNext和getNextGenerator函数,DataFetcherGenerator接口有三个是实现类,DataCacheGenerator、ResourceCacheGenerator和SourceGenerator,这里如果要了解网络资源的加载,查看SourceGenerator的实现,代码如下。

解析:在该函数中,也会先去缓存执行获取操作,这里的缓存是磁盘存储(第三级缓存),在通过一系列的判断,最终执行核心函数startNextLoad函数,下面具体看看。

解析:接口DataFetcher的实现类在项目中有多个,通过类名我们大概知道该类的主要作用是数据获取,而不同的是实现类是分别在不同的途径获取不同的资源方式,而网络的资源的实现类是HttpUrlFetcher,并在loadData中实现对资源的具体加载,而在loadData中则是通过HttpUrlConnection实现对网络资源的请求加载。


三:Glide三级缓存的设计

解析:在上面的模块中,本人通过分析Glide是如何加载一张图片,大概简述的一张网络图片的加载流程,在加载一张图片时,通常会先去缓存中查看是否包含该图片,也就是我们常说的需要先去三级缓存中查看,如果没有该图片,在去网络中获取该资源,从而节约网络流量,下面我们一起看看Glide 4.X是如何实现图片的三级缓存的,在执行Engine类的load函数时,首先会从内存缓存中查看时是否有该资源,下面看看具体代码:

解析:首先我们看到去内存缓存通过key获取缓存时,执行了两个函数loadFromActiveResources 和 loadFromCache,这里其实就是Glide实现的两种内存缓存,分别对应着一级缓存(ActiveResources)和二级缓存(MemoryCache),我们先看看具体缓存的实现。

ActiveResources类部分实现代码:

解析:通过上面的代码了解到,在一级缓存的是通过WeakReference + 引用队列 + Map哈希表的组合实现的,其具体代码实现原理是通过引用计数算法,记录当前界面中的活跃的资源应用,引用了一次,就添加1,没有引用了,就减少1,如果引用数为0时,就用数据结构中去除,并把资源放入二级缓存中,下面我们看看二级缓存实现。

解析:从上面的两段代码了解到,在执行二级缓存的存储结构主要是在LinkedHashMap中实现,如果缓存中有该资源,就从数据结构中去除相关资源,并存放到一级缓存中,我们上面已经了解到,一级缓存是使用了引用计数算法,当有引用的资源时,会对资源自动+1操作,下面我们具体了解LinkedHashMap该数据结构, 图解如下:

总结:

1、LinkHashMap继承HashMap,在HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问的数据的链表指针,具体就是先在链表中删除该节点,然后添加到链表头header之前,这样就保证了链表头header节点之前的数据都是最近访问的(从链表中删除并不是真的删除数据,只是移动链表指针,数据本身在map中的位置是不变的)。

2、HashMap无序;LinkedHashMap有序,可分为插入顺序和访问顺序两种,如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。

3、LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序。

4、LinkedHashMap是线程不安全的。

5、LruCache内部用LinkHashMap存取数据,在双向链表保证数据新旧顺序的前提下,设置一个最大内存,往里面put数据的时候,当数据达到最大内存的时候,将最老的数据移除掉,保证内存不超过设定的最大值。


两种内存缓存的总结:

1、弱引用的缓存会在内存不够的时候被清理掉,而基于LruCache的内存缓存是强引用的,因此不会因为内存的原因被清理掉。LruCache只有当缓存的数据达到了缓存空间的上限的时候才会将最近最少使用的缓存数据清理出去。

2、两个缓存的实现机制都是基于哈希表的,只是LruCahce除了具有哈希表的数据结构还维护了一个链表,而弱引用类型的缓存的键与LruCache一致,但是值是弱引用类型的。

3、除了内存不够的时候被释放,弱引用类型的缓存还会在Engine的资源被释放的时候清理掉。

4、基于弱引用的缓存是一直存在的,无法被用户禁用,但用户可以关闭基于LruCache的缓存。

5、本质上基于弱引用的缓存与基于LruCahce的缓存针对于不同的应用场景,弱引用的缓存算是缓存的一种类型,只是这种缓存受可用内存的影响要大于LruCache。


磁盘存储:当在内存缓存中,没有找到图片资源时,Glide会在磁盘中查找相关的图片资源,关于磁盘存储的实现,其实是使用了另外一个开源框架:DiskLruCache,Glide通过在SourceGenerator的cacheData函数执行磁盘缓存的查询,代码如下所示:

解析:上面第一段代码就是磁盘缓存的查询,而下面的类是Glide对开源项目DiskLruCache的封装,下面我们来大概了解一下该框架的实现原理:    DiskLruCache同样是利用LinkHashMap的特点,只不过数组里面存的Entry有点变化,Editor用于操作文件,DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容,因此,能够读懂journal文件对于我们理解DiskLruCache的工作原理有着非常重要的作用,DiskLruCache利用一个journal文件,保证了cache实体的可用性(只有CLEAN的可用),且获取文件的长度的时候可以通过在该文件的记录中读取,利用FaultHidingOutputStream对FileOutPutStream很好的对写入文件过程中是否发生错误进行捕获,而不是让用户手动去调用出错后的处理方法。      

备注:https://blog.csdn.net/guolin_blog/article/details/28863651(Android DiskLruCache完全解析,硬盘缓存的最佳方案)


四:Glide如何实现生命周期的监听        

       首先我们需要知道为什么我们在加载图片的时候需要监听生命周期,因为当我们启动一个界面加载图片时,假如图片才刚到加载队列里面,如果用户立刻关闭的相关界面,这时就需要把销毁的界面中包含的加载中的图片移除,所以我们需用通过界面的生命周期监听,获取到不同界面的当前状态,下面我们看看Glide时如何实现不同界面的生命周期监听的,代码如下:

解析:上面的代码我们了解到,Glide通过创建一个透明的Fragment(SupportRequestManagerFragment),这时候我们就觉得很奇怪,为什么监听界面生命周期不使用ActivityLifecycleCallback和FragmentLifecycleCallback,而是通过创建一个透明的Fragment来实现相关的生命周期监听,我们继续看代码,该问题可以好好思考一下。

解析:通过系统对Fragment相关生命周期的回调,把相关界面的回调监听数据存放在Set lifecycleListeners对象中,这样就间接通过透明Fragment实现各个界面生命周期的监听。


五:Glide是如何实现界面销毁时,界面相关请求关闭的    

       在界面销毁的时候,会通过透明Fragment的生命周期回调函数,监听到界面的销毁回调,这是就可以在回调函数中执行相关的请求关闭行为,具体代码如下:


六:Glide与Fresco的区别

Glide:

1、多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)

2、生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)

3、高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)

4、高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)

Fresco:

1、最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域,这个Ashmem区是一块匿名共享内存,Fresco将Bitmap像素放到共享内存去了,共享内存是属于native堆内存。

2、(Ashmem区)大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)

3、适用于需要高性能加载大量图片的场景。

4、Fresco对不同Android版本使用不同的方式去加载Bitmap,4.4-5.0,5.0-8.0,8.0以上,对应另外三个解码器。(具体代码查看PlatformDecoderFactory类)


总结:对于一般App来说,Glide完全够用,而对于图片需求比较大的App,为了防止加载大量图片导致OOM,Fresco 会更合适一些。并不是说用Glide会导致OOM,Glide默认用的内存缓存是LruCache,内存不会一直往上涨。


七:项目疑问汇总

问题一:为什么需要生成GlideApp

解析:使用一个注释处理器生成了一个API,允许应用程序访问的所有选项RequestBuilder,RequestOptions以及任何包含集成库在一个流畅的API,生成的API有两个目的:

1、集成库可以通过自定义选项扩展Glide的API。

2、应用程序可以通过添加捆绑常用选项的方法来扩展Glide的API。

问题二:Android 3.0到8.0之间Bitmap像素数据存在Java堆,而8.0之后像素数据存到native堆中,为什么?

解析:最大限度的减少加载图片造成的OOM问题。


问题三:在列表中加载图片为什么会错乱,Glide是如何处理的?

解析:由于RecyclerView或者ListView的复用机制,网络加载图片开始的时候ImageView是第一个item的,加载成功之后ImageView由于复用可能跑到第10个item去了,在第10个item显示第一个item的图片肯定是错的,常规的做法是给ImageView设置tag,tag一般是图片地址,更新ImageView之前判断tag是否跟url一致,可以在item从列表消失的时候,取消对应的图片加载任务,要考虑放在图片加载框架做还是放在UI做比较合适。

问题四:线程任务过多,Glide是如何处理的?

解析:列表滑动,会有很多图片请求,如果是第一次进入,没有缓存,那么队列会有很多任务在等待。所以在请求网络图片之前,需要判断队列中是否已经存在该任务,存在则不加到队列去,如果线程池的阻塞队列确实满了,则需要执行抛弃策略。


问题五:Glide 4.0相比Glide 3.0的变化?

解析:

1、相比于Glide 3,这里要多添加一个compiler的库,这个库是用于生成Generated API的。

2、将配置全部设置到了一个RequestOptions里面,并使用apply方法,应用这些配置信息。

3、Glide 4.x版本的缓存选项,有下面5种:(缓存策略)

DiskCacheStrategy.NONE: 表示不缓存任何内容。

DiskCacheStrategy.DATA: 表示只缓存原始图片。

DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。

DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。

DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。

4、内存缓存实现方案的改变:由原来的LruCache + SoftReference实现方案,改为WeakReference + 引用计数算法 + lruCache实现。

5、GlideApp:Generated API是Glide 4中全新引入的一个功能,它的工作原理是使用注解处理器 (Annotation Processor) 来生成一个API,在Application模块中可使用该流式API一次性调用到RequestBuilder,RequestOptions和集成库中所有的选项,简单说,就是Glide 4仍然给我们提供了一套和Glide 3一模一样的流式API接口,照顾老版本的用户的使用习惯。只不过需要把Glide关键字替换成GlideApp关键字。

6、自定义API:下面我来具体举个例子,比如说我们要求项目中所有图片的缓存策略全部都要缓存原始图片,那么每次在使用Glide加载图片的时候,都去指定diskCacheStrategy(DiskCacheStrategy.DATA)这么长长的一串代码,确实是让人比较心烦。这种情况我们就可以去定制一个自己的API了,定制自己的API需要借助 @GlideExtension 和 @GlideOption这两个注解,创建一个我们自定义的扩展类。


问题六:假如需要自己实现一个图片加载框架,需要了解什么技术点?

解析:

1、自定义线程池异步实现对图片的加载(ThreadPoolExecutor、生产者消费者模式)。

2、自定义Handler实现不同线程之间的通信。(了解Handler实现机制与常用接口)

3、图片的内存缓存(LruCache)和磁盘缓存(DiskLruCache)的实现,优化加载效率,避免OOM。

4、单例模式和建造者模式的使用,方便调用者使用和图片框架的相关配置实现。(SingleTon、Builder、链式调用)

5、实现相关界面的生命周期监听,实现界面销毁时,清除不需要的加载图片。(ActivityLifecycleCallback +

FragmentLifecycleCallbacks、透明Fragment + 观察者模式实现生命周期监听。

6、通过参数类型的不同,加载不同类型的图片资源(网络资源(webp、gif、png等)、本地资源、文件资源等)

7、网络请求框架的使用,执行网络图片的请求(okhttp、httpUrlConnection)和如何处理不同类型的图片资源(BitmapDecode、BitmapFactory.Option、gifDecode)

8、了解强引用、软引用、弱引用、虚引用的区别,如何更好的在内存缓存中使用相关引用,实现内存控制与用户体验达到最优实现。

9、不同的数据结构的深入了解,在什么场面下使用相关的数据结构,从而提高图片加载的整体执行效率。

(LinkedHashMap、Set、HashMap、ArrayList、BlockedQueue)



八:扩展阅读

1、https://www.jianshu.com/p/5dc3aaca2209(探索Android开源框架 - 4. Glide使用及源码解析)

2、https://www.jianshu.com/p/64fab1a85487(Android Glide 源码分析和学习)

3、https://www.jianshu.com/p/1ab5597af607(面试官:简历上最好不要写Glide,不是问源码那么简单)

4、https://blog.csdn.net/weixin_35355756/article/details/117543962(android gilde生命周期管理,Glide原理之Activity、Fragment生命周期监听(三))

5、https://blog.csdn.net/chengxuyuan22/article/details/115345179(Glide原理之史上最全的(一))

6、http://events.jianshu.io/p/448c840f583c(Glide 新版中GlideApp(AppGlideModule)配置方法)

7、https://blog.csdn.net/guolin_blog/article/details/28863651(Android DiskLruCache完全解析,硬盘缓存的最佳方案)

8、https://blog.csdn.net/system_err/article/details/77049684(Glide 4.0相比Glide 3.0及使用详解)

9、https://blog.csdn.net/guolin_blog/article/details/78582548(Android图片加载框架最全解析(八),带你全面了解Glide 4的用法)

你可能感兴趣的:(Glide源码解析(4.X版本))