Glide完全解读

news/2024/7/7 21:30:44

一,概述

glide作为android流行的图片加载框架,笔者认为有必要对此完全解读。glide提供了三级缓存、生命周期Destroy后自动移除缓存、自动适配ImageView,以及提供了各种对图片修饰的操作,如剪裁等。本文通过最简单的使用,挖掘出with、load、into三大核心函数的源码逻辑,进一步理解Glide设计思路。读者可顺着笔者粗糙的源码流程更深入地解读。

二,简单使用

如上,笔者不赘述。

三,核心流程

1,with

众所周知,Glide支持监听生命周期,那么它是怎么做的呢?我们看下with函数的重载,返回值是RequestManager。

笔者查阅官方注释,推测RequestManager负责调度一次请求,如停止、启动或重启,这需要与生命周期者配合。因此,笔者进一步推测RequestManager与生命周期者,即android中的Activity、Fragment、Application是1:1的关系。

从以上RequestManager提供的public方法来看,提供了诸如加载load,pause,resume,clear等调度函数。onStart和onStop是其回调函数,如下

在onStart时resumeRequest,onStop时清除或pauseRequest,取决于clearOnStop字段的设置。

笔者继续更加with函数,看下RequestManager如何创建。

笔者以with重载函数参数为context为例,

首先调用getRetriever获得RequestManagerRetriever,这是负责创建和复用RequestManager的工厂类,全局单例。

RequestManagerRetirever在Glide的构造函数中被初始化,因Glide是单例模式,因此RequestManagerRetirever也是单例。我们跟进到RequestManagerRetirever#get方法,也有很多重载,保持glide#with重载一样。

笔者仍以context为例,

(1)如果context是FragmentActivity,且,当前调用with函数来自主线程,我们跟进

来到重载get(FragmentActivity),如果当前是子线程则进入重载get(Application)。同上述流程,我们跟进getOrCreate,注意传入了FragmentActivity的FragmentManager。在此之前,我们看下LifecycleRequestManagerRetriever的成员。

根据lifecycleToRequestManager这个Map,笔者验证了RequestManager与生命周期者是1:1关系。

接着看getOrCreate

如果不存在RequestManager,则new一个LifeCycleLifeCycle,lifecycle传入,

LifeCycleLifeCycle实现了LifecycleObserver接口,这样就能监听到lifecycle的生命周期了。通过工厂方法build创建,其实现在RequestManagerRetriever,简单new了一个RequestManager,

我们紧接后续逻辑,通过isParentVisible判断是否调用result.onStart回调,result就是RequestManager了,onStart即恢复请求,笔者这是第一次从with进入,因此没有请求。

唉,看到这,笔者发现glide优化了,毕竟在旧版本,glide是通过在FragmentActivity中创建一个隐藏的空Fragment去监听Activity的生命周期,当时笔者就有疑问,为什么不直接监听呢?

笔者注意到,lifeCycle走到onDestroy时自动移除对应的RequestManager。

笔者也注意到,SupportRequestManagerTreeNode这个类,看下实现,

通过getDescendants可知,获得当前FragmentManager的所有RequestManager,包括子片段。主要在调度Request Resume或Pause使用,其意义是递归式地暂停或恢复当前LifeCycle及其附着的子LifeCycle所对应的RequestManager。

笔者继续看其他get方法的重载实现,

再看下Application的get重载,

可知applicationManager全局单例,并且由于传入了ApplicationLifecycle,自然调用不到RequestManager的onStart和onStop方法,没有自动暂停恢复功能。

综上,笔者从多个get方法得出如下结论:

(1),如果传入Applicaton,全局单例,无生命周期监听功能。

(2),如果传入Activity,则直接调用到(1)。

(3),如果传入FragmentActivity,如果在子线程调用到(1),否则创建或复用该FragmentActivity对应的RequestManager,能监听生命周期。

(4),如果传入Fragment,将当前Fragment作为生命周期对象监听,并且创建或复用一个RequestManager。

(5),如果传入View,通过其getContext决定调用到(1)或(3)或(4)。

即with方法决定了在当前上下文中获取的复用RequestManager。

2,load

在load之前,笔者首先看下其as方法,这是RequestManager创建一次RequestBuilder的起点,需读者注意,RequestBuilder继承了BaseRequestOptions类,

可以接收的参数有

(1)BItmap.class,

(2)Drawable.class,

(3)File.class,

(4)GitDrawable.class,

保存至transcodeClass中,并且在构造中根据其类型,获得一些默认的options,这个读者可自行了解。

笔者看下load方法的多个重载,假设以Drawable重载为例跟进

最终都会调用到loadGeneric,并且将load传入的参数保存到model中,以构造者设计方式设计此RequestBuilder,

RequestBuilder其重要是apply函数,

负责保存对图片的操作,每个操作对应RequestOptions类,方法如下

看到这,load方法就结束了。做了四件事情,

(1)创建一个RequestBuilder对象,

(2)保存图片目标类,记录在RequestBuilder#transcodeClass。

(3)保存图片来源,记录在RequestBuilder#mode。

(4)保存对图片的各种RequestOptions操作,记录在RequestBuilder的Set集合中。

对于缓存的实现,主要在RequestOptionsr和RequestBuilder的父类BaseRequestOptions中,通过一系列成员保存option操作,读者可自行研究。对于缓存策略,笔者将在into中讲述。

3,into

target必须是实现了Target接口,看下Target定义如下,

笔者在这里理解为资源相关的回调,如资源加载开始、失败、完成、清除等,其接收一个泛型字段,TranscodeType,这个和RequestBuilder中的transcodeClass相同。

再看下Target的实现类,

笔者在此发现了ImageViewTarget这个类,因此推测into(ImageView)是将ImageView封装进ImageVIewTarget对象,其根据transcodeClass决定使用BitmapImageVIewTarget还是DrawableImageVIewTarget。另外,Target接口继承了LifecycleListener接口,笔者推测此处与RequestManager联合,做一些事情,如动画相关等等等。

笔者跟进Into,最终全部调用到private的into重载,

笔者注意到传入callbackExecutor是Executor.mainThreadExecutor。

通过buildRequest方法创建一个Request,Request是一个接口,看下定义,

很简单,负责一次请求的开始、清除、暂停,以及状态获取。只有如下三个实现,

那么,具体返回哪个Request呢,笔者继续跟进buildRequestRecursive。

现创建mainRequest,其是正常请求,如果我们设置了加载失败的相关选项,则创建一个ErrorRequestCoordinator,将正常请求保存到primary成员中。当加载失败时,调用error的begin,如下,

笔者对失败Request不展开说,跟进buildThumbnailRequestRecursive,创建了一个ThumbnailRequestCoordinator。看类名,这是和所谓图相关的Request,跟进,

发现方法内通过obtainRequest创建了一个fullRequest,跟进,

又创建了一个SingleRequest,并且设置到ThumbnailRequestCoordinator的full成员。以及下面逻辑设置SingleRequest到thumb成员。

笔者在此总结下,通过RequestBuilder#buildRequestRecursive方法,最先创建一个ErrorRequestCoordinator,其primary成员保存ThumbnailRequestCoordinator(如果设置了加载失败的选项图),error保存错误选项ThumbnailRequestCoordinator(通过errorBuilder创建)。然后通过buildThumbnailRequestRecursive创建ThumbnailRequestCoordinator,fill成员保存通过obtainRequest创建的SingleRequest。thumb成员也是通过不同的RequestBuilder#obtainRequest创建的SingleRequest。

层层包括Request,这就用到了装饰设计模式。笔者画了以下类图供读者理解。

笔者回到主线,从RequestManager#track出发,

笔者假设没有paused,且知道这一个request是ErrorRequestCoordinator,跟进,

当primary没有运行,调用到ThumbnailRequestCoordinator#begin,跟进,

第一次调用thumb#begin,先请求缩略图,随后调用full#begin,请求完整请求。thumb和full均是SingleRequest,跟进。

model为null,非合理源,回调onLoadFailed,

如果正在running,抛出异常;如果已经完成,直接回调onResourcReady,数据源传入MEMORY_CACHE,毕竟确实在内存中(只有开启内存才会有此调用)。

笔者继续跟进,

onSizeReady,传入剪裁大小,最后调用onLoadStart,获取到预览图,通知Target。

笔者从注释知onSizeReady开始进入异步,因此进入核心方法onSizeReady,

将状态标记为RUNNING,调用engine.load方法,这是什么呢?通过该类成员猜测,这是干具体事的类。

Engine存在如cache(LRU),activeResources等缓存成员,解码decodeJobFactory等。关于该类具体工作,笔者借下文讲解。

4,Engine#load

通过load方法,传入资源model,宽高,返回类型transcodeClass,一些选项等,如是否开启缓存,以及callbackExecutor。随后通过KeyFactory生成key值,这个笔者推测与缓存相关。

跟进loadFromMemory,

如果开启跳过缓存,直接返回null,

先从ActiveResources中获取,如果没有再从Cache中获取,如果没有返回null,

我们看下loadFromActiveResourcesResources,

如果获取到不为null的值,调用acquire引用计算+1。再看下loadFromCahce方法,

从cache中获取(注意,直接从cache中remove),如果获取到引用计数+1,并且放入ActiveResources(这里面的资源计数一定大于0,表示正在被使用)中。

这里的cache是什么呢?答案是默认LruResourceCache,可以通过Glide.Builder#setMemoryCache定义用户自己的缓存,在此不赘述。

笔者注意到Glide对资源管理采用引用计数的方式,因此看下计数到0时发生什么?

调用listener#onResourceRelease,跟进看下实现,

从ActiveResources中移除,并且如果资源可缓存,存放入cache中,否则recycle掉。

笔者回到主线,如果没有缓存,调用到waitForExistingOrStartNewJob方法,

跟进,

创建一个引擎Job,创建一个解码Job

加入到jobs中国。调用start方法,注意到addCallback传入了回调和回调线程。

跟进start,

获得一个GlideExecutor,根据策略决定用哪个,笔者直接跟进DecodeJob的run方法,

根据runReason有三种状态执行,如初始化,切换资源服务,解码。第一次进入现初始化,调用runGenerators,

做了些初始化工作,调用startNext方法,generator有三个实现,如下,

至于获得哪一个,得从策略决定,

更具体的笔者暂不深跟,只要知道这里面就会请求到网络或磁盘即可。当资源准备然后切换到SWITHC_TO_SOURCE_SERVICE来解析资源,

解完成后,

切换回调线程(一般是主线程),通知资源调用完毕。

层层回调到SingleRequset#onResourceReady方法,进一步调用到Target的onResourceReady方法,

即完成了一次请求。如果是ImageVIewTarget,则进一步调用ImageView#setDrawalbe或setBitmap方法。

笔者跟到这,再回溯一下内存缓存。

如果再次进行请求,并且开启了内存缓存,怎么走呢?

由于target已存在内存,同一个request调用时,直接通过previous.begin调用,由于上次request加载完毕,因此,我们就通过内存缓存的形式返回了。

四,裁剪相关

为了防止加载大图片导致内存溢出,glide提供了裁剪功能,即传入指定宽高或sizeMultiplier

,可避免加载原图大小而崩溃,

在解码时,会根据width、height去解码,因此可以避免大图片问题崩溃。

五,恢复/暂停加载

在RequestManager中,由于监听了lifeCycler,

1,pause

笔者假设用户未设置clearOnStop,进入pauseRequests,笔者以SingleQuest为例,

简单cancel,如果有release,则释放。

clearOnStop如果为true,就清空所有的Target,间接调用到Request#clear方法,

2,resume

如下图,仍调用到Request#begin,不赘述。

六,低内存释放缓存

在Application的onLowMemory回调中,写入此方法,

或读者可自定义一个MemoryCache,内部采用SoftRefenrce方式设置到glide中。

我们看下clearMemory实现,

这三个缓存池全局单例,通过引用方式传入到Engine(Engine也是全局单例,Request所使用到的Engine来自GlideContext)。

另外,笔者提醒读者注意,只清除memroyCache、bitmapPool、arrayPool的缓存,ActiveResources不会清除。

笔者在此提问,Glide默认的缓存大小是多少呢?有兴趣的读者可以阅读MemorySizeCalculator,glide会根据当前设备ram是否是低内存ram,以及屏幕宽高动态计算出一个合适的大小。

七,三级缓存

所谓glide提供了哪三级缓存?根据核心流程的源码走读,我们看到了内存缓存。

glide提供内存缓存,磁盘缓存(持久化到文件系统中),网络缓存这三种。

内存缓存上文已经介绍过,我们接下来介绍下磁盘缓存入口,

在DecodeJob#notifyEncodeAndRelease处,

glide使用了DiskLruCache来存储,我们跟进,

如果current不为null,表示缓存存在,直接返回,不然通过editor写入到磁盘。

笔者下面介绍网络获取相关,

当需要从网络中获取资源时,glide会调用HttpUrlFetcher类,

连接后,如果状态ok,拿到InputStream,通过getStreamForSuccessfulRequest发送处url Connect,并且获取inputStream,

最后解析stream,存储LRUResoureCache中。

笔者粗陋地理解所谓的网络缓存要么是缓存在网络层,要么是从网络中解析的数据到了内存缓存中。


http://lihuaxi.xjx100.cn/news/2000014.html

相关文章

STM32——ADC

STM32——ADC 1.ADC介绍 ADC是什么? 全称:Analog-to-Digital Converter,指模拟/数字转换器! ADC性能指标 量程:能测量的电压范围分辨率:ADC能辨别的最小模拟量,通常以输出二进制数的位数表示&#xf…

大创项目推荐 题目:基于深度学习卷积神经网络的花卉识别 - 深度学习 机器视觉

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 基…

【Spring连载】使用Spring Data访问Redis(一)----快速指南

【Spring连载】使用Spring Data访问Redis&#xff08;一&#xff09;----快速指南 一、导入依赖二、Hello World程序 一、导入依赖 在pom.xml文件加入如下依赖就可以下载到spring data redis的jar包了&#xff1a; <dependency><groupId>org.springframework.boot…

npm安装下载修改镜像源

问题描述一 npm install 时&#xff0c;报错&#xff1a;npm ERR! network request to https://registry.npmjs.org/postcss-pxtorem failed, reason: connect ETIMEDOU&#xff0c;这是因为默认npm安装会请求国外的镜像源&#xff0c;导致下载缓慢容易断开请求下载失败的 np…

Flink 添加 / 部署 Jar 包的若干注意事项

Flink 添加 / 部署 Jar 包可根据 Jar 包的声明周期、作用范围选择不同的附属方式&#xff0c;从实际应用上来看&#xff0c;可以分成以下几种场景&#xff1a; 普遍使用的框架或基础设施级别的 Jar 包&#xff0c;例如 Kafka、Hive、Hudi 等 Connector 的Jar 包&#xff0c;应…

Windows断开映射磁盘提示“此网络连接不存在”,并且该磁盘直在资源管理器中

1、打开注册表编辑器 快捷键winR 打开“运行”&#xff0c; 输入 regedit 2、 删除下列注册表中和无法移除的磁盘相关的选项 \HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\ 3、打开“任务管理器”&#xff0c;重新启动“Windows资源…

刷力扣题过程中发现的不熟的函数

C中不熟的函数 1.memset() 头文件&#xff1a;<string.h> void *memset(void *s,int c,unsigned long n); 为指针变量s所指的前n个字节的内存单元填充给定的int型数值c 如&#xff1a; int a[10]; memset(a,0,sizeof(a)); //将数组a中的数全部赋值为02.sort() &#…

flutter module打包成framework引入原生工程

Flutter - 将 Flutter 集成到现有项目&#xff08;iOS - Framework篇&#xff09; 本篇文章大幅参考了 caijinglong 大佬的总结文章&#xff1a; 把flutter作为framework添加到已存在的iOS中[1] 用 Flutter 来开发&#xff0c;从来都不可能是新开的一个纯 Flutter 项目&#xf…