Glide完全解读

news2024/11/18 3:41:26

一,概述

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://www.coloradmin.cn/o/1419500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vim实战:使用 Vim实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度,DP多卡,EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

光学3D表面轮廓仪服务超精密抛光技术发展

随着技术的不断进步,精密制造领域对材料表面的处理要求越来越高,超精密抛光技术作为当下表面处理的尖端技术,对各种高精密产品的生产起到了至关重要的作用,已广泛应用于集成电路制造、医疗器械、航空航天、3C电子、汽车、精密模具…

Django知识随笔

目录 1.如何再ajax中传输post数据? 2.在form表单中使用jquery序列化,input框过多。 1.如何再ajax中传输post数据? 在ajax传递的那个网址,会调用你路由的视图函数,在视图函数上面加一句 csrf_exempt 。写上之后会有提…

Docker私有仓库搭建

目录 搭建本地私有仓库 Docker--harbor私有仓库部署与管理 Harbor 简介 什么是Harbor Harbor的特性 Harbor的构成 Harbor 部署 部署 Docker-Compose 服务 ​编辑部署 Harbor 服务 启动 Harbor 进入浏览器http://192.168.20.10进入harbor的客户端 搭建本地私有仓库 …

Shell中sed编辑器

1.简介 sed是一种流编辑器,流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。 sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在一个 命令文本文件中。 2.sed编辑器的工作流程 sed…

12.从项目经理的生存哲学到适配器模式(Adapter Pattern)

如果这个世界没有了项目经理,事情的发展可能并不会如同想象中一样美好,相反,对于开发人员来说可能是噩梦的开始。 比如: 客户因为几个需求的具体实现大发雷霆,甚至开始恶语相向,一通含ma量极高的“斯伯坦语…

【EEG信号处理】ERP相关

ERP,全称为event-related potential,中文是事件相关电位。 首先要明确的一点是,ERP是根据脑电图EEG得到的,他是EEG的一部分,是最常用的时域分析方法 可能有一部分是介绍不到的,望谅解 在维基百科中给的定义…

Vue3的v-model

目录 基本用法 底层机制 v-model 的参数 多个 v-model 绑定 处理 v-model 修饰符 带参数的 v-model 修饰符 例子 总结:Vue 3 中的 v-model 指令与 Vue 2 中的 v-model 相比有一些变化和改进。最显著的变化是,在 Vue 3 中,v-model 可以…

matlab appdesigner系列-仪器仪表4-开关、开关(切换)、开关(翘板)

开关、开关(切换)、开关(翘板),可进行On和Off两种状态切换 示例:开关开启时,可通过滑块调整表盘数值,并有提示框提示 开关关闭时,滑块、表盘数值清零,并有提…

【DDD】学习笔记-理解上下文映射

一个软件系统通常被分为多个限界上下文,这是运用“分而治之”思想来降低业务复杂度的有效手段,设计的难题往往会停留在“如何分”,然而限界上下文之间的“怎么合”问题同样值得关注,分与合遵循的还是软件设计的最高原则——高内聚…

系列五十、idea父子项目忽略部分文件

一、idea父子项目忽略部分文件 **/mvnw **/mvnw.cmd **/.mvn **/target/ .idea **/.gitignore

一进一出模拟量信号隔离变送器

一进一出模拟量信号隔离变送器 捷晟达科技推出一进一出模拟量信号隔离变送器 深圳捷晟达科技推出一款具有隔离,放大,转换保护功能的一进一出的小型隔离变送器设备,该设备可以把模拟量(4-20mA/0-10V等)标准信号转换用户需要的信号,该产品具有抗EMC干扰,可以有效的保护后级设备安…

【MySQL】双写、重做日志对宕机时脏页数据落盘的作用的疑问及浅析

众所周知,双写机制、重做日志文件是mysql的InnoDB引擎的几个重要特性之二。其中两者的作用都是什么,很多文章都有分析,如,双写机制(Double Write)是mysql在crash后恢复的机制,而重做日志文件&am…

网安文件包含漏洞

文件包含概念: 开发人员通常会把可重复使用的函数写到单个文件中,在使用某些函数时,直接调用此文件,而无需再次编写,这种调用文件的过程一般被称为包含。为了使代码更加灵活,通常会将被包含的文件设置为变…

docker+jekins+maven+ssh 持续集成交付部署 jar包

一. docker环境搭建,此处略过。 二. docker部署jekins 2.1 拉取镜像,挂载工作目录,xxxx为宿主机指定工作目录 docker pull jenkins/jenkins docker run -d -p 8080:8080 -p 50000:50000 --name jenkins --privilegedtrue -v xxxxxxxxxx:/var/jenkins…

源码安装nginx并提供服务脚本

一、下载nginx ①官网复制下载链接 ②在Linux中下载 [rootopenEuler2 ~]# wget -c https://nginx.org/download/nginx-1.24.0.tar.gz 二、解压并指定路径 [rootopenEuler2 ~]# tar xf nginx-1.24.0.tar.gz -C /usr/local/src/ 三、安装依赖 dnf install -y gcc gcc-c mak…

使用plotly dash 画3d圆柱(Python)

plotly3D (3d charts in Python)可以画3维图形 在做圆柱的3D装箱项目,需要装箱的可视化,但是Mesh (3d mesh plots in Python)只能画三角形,所以需要用多个三角形拼成一个圆柱(想做立…

vue实现跳转传参查询

vue实现跳转传参查询: 应用场景:外部链接携参跳转目标页时,避免多次输入查询信息查询 目标需求:登录及非登录状态均可跳转自动查询 避坑指南:token失效时需要重新缓存及路由导航缓存判断 简单实现:缓存信息&#xff0c…

PeakCAN连接到WSL2 Debian

操作步骤 按照以下步骤进行操作: 在Windows下安装PeakCAN驱动并安装,地址是https://www.peak-system.com/PCAN-USB.199.0.html?&L1 在Windows下安装usbipd,地址是https://github.com/dorssel/usbipd-win/releases,最新版是…

Elasticsearch:如何为 Elastic Stack 配置 AI Assistant

了解并安装 Elastic AI Assistant Elastic 推出了 Observability AI Assistant,这是一款利用生成式 AI 来增强你的 Observability 体验的强大工具。 该 AI 助手由 OpenAI 或 Azure OpenAI 服务的连接器提供支持,可带来上下文洞察和聊天功能,…