Glide完全解读

一,概述

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中。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/357322.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot通过配置文件支持数据库自定义表名

直接上干货: 例如一个叫xxx的项目,yml文件里加上这段 xxxproject:db:xxxTable: xxx_dbname #自定义的数据库表名创一个Configuration类放表名和Mapper // XxxProjectAutoConfiguration.javaConfiguration MapperScan(basePackages "cn.com.xxxp…

PageHelper分页插件-以三层架构模型开发为例

文章目录 1、简介2、使用2.1、导入2.1.1、SpringBoot2.1.2、非SpringBoot 2.2、controller2.3、service2.4、mapper ​🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、…

Spring Boot导出EXCEL 文件

主要功能:实现java导出excel到本地 JDK版本&#xff1a;openJDK 20.0.1 依赖pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchem…

详细讲解Java中的Properties类

目录 前言1. 基本知识2. 代码示例3. Demo 前言 使用Properties出现中文乱码可看我这篇文章&#xff1a;properties出现中文乱码解决方法&#xff08;万能&#xff09; 1. 基本知识 Properties 类是 Java 中用于处理配置文件的工具类&#xff0c;它继承自 Hashtable 类&#…

计算机网络——IP协议

前言 网络层的主要负责地址分配和路由选择,ip负责在网络中进行数据包的路由和传输。 IPv4报文组成&#xff08;了解&#xff09; IPv4首部&#xff1a;IPv4首部包含了用于路由和传输数据的控制信息&#xff0c;其长度为20个字节&#xff08;固定长度&#xff09;。 版本&#…

Mysql 插入数据

1 为表的所有字段插入数据 使用基本的INSERT语句插入数据要求指定表名称和插入到新记录中的值。基本语法格式为&#xff1a; INSERT INTO table_name (column_list) VALUES (value_list); 使用INSERT插入数据时&#xff0c;允许列名称列表column_list为空&#xff0c;此时&…

选型 之 工业相机篇

一、概述 23年24年行情不会好&#xff0c;公司各种想办法裁员&#xff0c;在大陆这个大熔炉中只能不断地提炼。我个人主要是在工业领域做2D图像算法和3D算法&#xff0c;但是现在出去都需要全能人才 方案、算法、运动控制等&#xff0c;我目前最大的短板就是方案&#xff0c;在…

Ubuntu本地部署Nextcloud并结合内网穿透实现远程访问搭建个人云盘

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 摘要1. 环境搭建2. 测试局域网访问3. 内网穿透3.1 ubuntu本地安装cpolar3.2 创建隧道3.3 测试公网访…

USB-C显示器:未来显示技术的革新者

随着科技的不断发展&#xff0c;显示技术也在不断进步&#xff0c;而USB-C显示器作为最新的显示技术&#xff0c;正在引领着显示行业的发展潮流。USB-C显示器具有许多优点&#xff0c;如高速传输、便捷连接、节能环保等&#xff0c;使其成为未来显示技术的革新者。 一、USB-C显…

leetcode hot100跳跃游戏Ⅱ

本题和上一题还是有不一样的地方&#xff0c;这个题中&#xff0c;我们需要记录我们跳跃的步数并尽可能的满足最小的跳跃步数到达终点。 那么我们还是采用覆盖范围的概念&#xff0c;但是我们需要两个&#xff0c;一个是在当前位置的覆盖范围&#xff0c;另一个是下一步的覆盖…

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

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

多种协议转IEC104网关BE115

随着电力系统信息化建设和数字化转型的进程不断加速&#xff0c;对电力能源的智能化需求也日趋增强。健全稳定的智慧电力系统能够为工业生产、基础设施建设以及国防建设提供稳定的能源支持。在此背景下&#xff0c;高性能的工业电力数据传输解决方案——协议转换网关应运而生&a…

centos7上安装mysql5.7并自定义数据目录路径

1、卸载mariadb rpm -qa |grep mariadb #查出来的结果是mariadb-libs-5.5.68-1.el7.x86_64 rpm -e mariadb-libs-5.5.68-1.el7.x86_64 --nodeps #卸载查到的结果 2、官网下载响应的tar.gz包&#xff0c;比如mysql-5.7.38-el7-x86_64.tar.gz &…

回归预测 | Matlab实现CPO-LSTM【24年新算法】冠豪猪优化长短期记忆神经网络多变量回归预测

回归预测 | Matlab实现CPO-LSTM【24年新算法】冠豪猪优化长短期记忆神经网络多变量回归预测 目录 回归预测 | Matlab实现CPO-LSTM【24年新算法】冠豪猪优化长短期记忆神经网络多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CPO-LSTM【24年新算…

js实现填涂画板

文章目录 1实现效果2 实现代码 凑个数&#xff0c;存粹是好玩儿&#xff0c;哈哈... 1实现效果 最上方一栏&#xff1a; 左侧是颜色按钮&#xff0c;点击选中颜色&#xff0c; 中间是功能按钮&#xff0c;重置颜色、清空画板、回退、涂改液&#xff08;填涂色置为白色&#xff…

数字人的不足

在数字化环境中对于事物或情况的感知能力存在缺陷。这可能涉及到人类对数字化信息的理解、利用和应对能力方面的问题&#xff0c;也可能涉及到数字化系统或技术本身的局限性。在当今数字化时代&#xff0c;人们对数字化信息的感知和理解至关重要&#xff0c;因为数字化技术已经…

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测(matlab)

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测&#xff08;matlab&#xff09; CDO-CNN-BiLSTM-Attention切诺贝利灾难优化器优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 切诺贝利灾难优化器Chernobyl Disaster Optimizer (CDO)是H. Shehadeh于202…

4D毫米波雷达——ADCNet 原始雷达数据 目标检测与可行驶区域分割

前言 本文介绍使用4D毫米波雷达&#xff0c;基于原始雷达数据&#xff0c;实现目标检测与可行驶区域分割&#xff0c;它是来自2023-12的论文。 会讲解论文整体思路、输入分析、模型框架、设计理念、损失函数等&#xff0c;还有结合代码进行分析。 论文地址&#xff1a;ADCNe…

为什么说语言的主要作用不是交流而是思考

一般人常常以为&#xff0c;语言最重要的作用是帮助人们表达思想和情感。但最近偶然看到对乔姆斯基的一个采访&#xff0c;他认为&#xff1a;语言的主要作用不是交流&#xff0c;而是思考的工具&#xff0c;即语言是帮助人们组织和理解思维的过程。以下是几点说明为什么说语言…

Vue-43、Vue中组件自定义事件

1、给学生绑定atguigu事件 2、在组件内触发事件 第二种写法 传多个参数。 解绑 解绑一个事件 解绑多个自定义事件 this.$off([xxx1,xxx2]);解绑所有事件 this.$off();总结