【开源框架】Glide的图片加载流程

本篇文章从Glide 4.11源码入手,简单的分析整个图片请求的流程,本着 ”只见树林,不见树木“ 的原则,宏观请求流程,不细究实现细节(细节留坑埋点,之后慢慢写)

引入依赖

以下的所有分析都是基于此版本的Glide分析

//引入第三方库glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

分析

Glide的使用就是短短的一行代码

Glide.with(this).load("xxx").into(imageView);//拆分成三步
RequestManager with = Glide.with(this);RequestBuilder<Drawable> load = with.load("");load.into(iv);
  1. 首先通过构造Glide的单例对象
  2. 调用with给每一个RequestManager绑定一个空白的Fragment来管理图片加载的生命周期
  3. 构建Request对象(真正的实现类SingleRequest)
  4. 请求之前先检测缓存
    1. 先检测活动缓存
    2. 在检测内存缓存
  5. 没有缓存就构建一个新的异步任务
  6. 检测有没有本地磁盘缓存
  7. 没有磁盘缓存,就通过网络请求,返回输入流InputStream
  8. 解析输入流InputStream进行采样压缩,最终拿到Bitmap对象
  9. 对Bitmap进行转换成Drawble
  10. 构建磁盘缓存DiskCache
  11. 构建内存缓存
  12. 最终回到ImageViewTarget显示图片

image.png

with(如何实现生命周期的管控)

调用with方法

public static RequestManager with(@NonNull FragmentActivity activity) {return getRetriever(activity).get(activity);
}

会来到RequestManagerRetriever类的get方法中,在这里区分主线程还是在子线程中使用with。
如果在子线程中,绑定的是app的生命周期,在主线程中会将图片的加载与当前activity的生命周期绑定。
这也是为什么不能在子线程中使用Glide的原因,生命周期的管理会失效。

public RequestManager get(@NonNull FragmentActivity activity) {if (Util.isOnBackgroundThread()) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);FragmentManager fm = activity.getSupportFragmentManager();return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));}}

最终会返回一个RequestManager对象。

load

调用load最终会返回一个RequestBuilder对象
load负责做什么?
image.png

into

into创建请求

RequestBuilder

RequestBuilder中的into方法,首先会创建一个请求,它是一个接口,真正的实现类是SingleRequest

private <Y extends Target<TranscodeType>> Y into(@NonNull Y target,@Nullable RequestListener<TranscodeType> targetListener,BaseRequestOptions<?> options,Executor callbackExecutor) {······//1.创建一个请求//这里的Request是一个接口,实际构造的对象是SingleRequest实现类Request request = buildRequest(target, targetListener, options, callbackExecutor);······//2.requestManager.clear(target);target.setRequest(request);//3.继续跟踪tarck()requestManager.track(target, request);return target;}

RequestTracker

一个用于跟踪、取消和重新启动正在进行的、已完成的和失败的请求的类
跟踪track方法最终调用的是runRequest方法。
其中有两个列表:

  • requests:运行中的队列
  • pendingRequests:等待中的队列
public void runRequest(@NonNull Request request) {//1.将请求加入到运行队列中requests.add(request);//2.请求没有暂停就调用SingleRequest的begin方法if (!isPaused) {request.begin();//3.请求暂停了就加入到等待运行的队列中} else {request.clear();pendingRequests.add(request);}}

SingleRequest

SingleRequest类中begin函数是添加了synchronized,保证在多线程下的线程安全
onSizeReady方法,最终返回调用的engine.load()

@Overridepublic void begin() {synchronized (requestLock) {······status = Status.WAITING_FOR_SIZE;if (Util.isValidDimensions(overrideWidth, overrideHeight)) {//1.继续深入此方法onSizeReady(overrideWidth, overrideHeight);} else {target.getSize(this);}······}}

Engine(活动和内存缓存)

这时已经来到了Engine类的load方法中

 public <R> LoadStatus load(GlideContext glideContext,Object model,Key signature,int width,int height,······) {long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;//1.得到一个唯一的key,用于唯一标识图片,用于缓存读取EngineKey key =keyFactory.buildKey(model,signature,width,height,······);//2.将key传入,从缓存中获取图片EngineResource<?> memoryResource;synchronized (this) {memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);if (memoryResource == null) {//4.如果缓存中没有则加载return waitForExistingOrStartNewJob(glideContext,model,signature,width,height,······key,startTime);}}//3.存在图片缓存,则直接将它回调出去cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);return null;}

详看loadFromMemory方法
在这里存在活动缓存和内存缓存两级缓存,这两级缓存都是运行时缓存,当APP进程被杀,这这两级缓存是不再存在的。
活动缓存是:直接面向用户正在被展示的图片,是一个弱引用对象,当弱引用对象被回收之后,会把它持有的图片资源放到内存缓存中

@Nullableprivate EngineResource<?> loadFromMemory(EngineKey key, boolean isMemoryCacheable, long startTime) {······//1.从活动缓存中获取EngineResource<?> active = loadFromActiveResources(key);if (active != null) {return active;}//2.活动缓存没有,才从内存缓存中获取EngineResource<?> cached = loadFromCache(key);if (cached != null) {return cached;}//3.都没有则直接退出return null;}

回过头再看waitForExistingOrStartNewJob方法,这个方法是没有检测到缓存时才会被调用。
首先会再次检测磁盘缓存中是否存在,不存在则创建异步任务

private <R> LoadStatus waitForExistingOrStartNewJob(GlideContext glideContext,Object model,Key signature,int width,int height,······EngineKey key,long startTime) {//1.get磁盘缓存EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);if (current != null) {current.addCallback(cb, callbackExecutor);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}//2.执行图片各类操作的jobEngineJob<R> engineJob =engineJobFactory.build(key,isMemoryCacheable,useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache);//3.真正需要执行的任务,最终放入到engineJob中执行DecodeJob<R> decodeJob =decodeJobFactory.build(glideContext,model,key,······);jobs.put(key, engineJob);engineJob.addCallback(cb, callbackExecutor);//4.将decodeJob放入engineJob开始执行engineJob.start(decodeJob);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}

engineJob启动decodeJob的执行。最终通过线程池去执行decodeJob操作,可见decodeJob肯定是Runnable的实现类。

public synchronized void start(DecodeJob<R> decodeJob) {this.decodeJob = decodeJob;GlideExecutor executor =decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();executor.execute(decodeJob);}

DecedeJob

执行的是decodeJob中的run方法

@Overridepublic void run() {·······try {//1.重点关注此方法runWrapped();} catch (CallbackException e) {throw e;} catch (Throwable t) {·····} finally {}

runWrapped主要是对不同任务的区分。
具体执行哪一个看此篇文章:https://www.jianshu.com/p/faeeb7bb39a3

private void runWrapped() {switch (runReason) {//1.首次初始化并获取资源case INITIALIZE:stage = getNextStage(Stage.INITIALIZE);currentGenerator = getNextGenerator();runGenerators();break;//2.从磁盘缓存获取不到数据,重新获取case SWITCH_TO_SOURCE_SERVICE:runGenerators();break;//3.获取资源成功,解码数据case DECODE_DATA:decodeFromRetrievedData();break;default:throw new IllegalStateException("Unrecognized run reason: " + runReason);}}

不管是 INITIALIZE 初始化还是 SWITCH_TO_SOURCE_SERVICE 从磁盘缓存获取不到数据进行重试,都是要调用 runGenerators 来获取数据。我们先来看getNextGenerator,在首次初始化时会调用这个方法。可以看到这里由很多的XXXGenerator
DataFetcherGenerator的三个实现子类:

  • ResourceCacheGenerator:从磁盘缓存中获取原始资源处理后的Resource资源
  • DataCacheGenerator:从磁盘缓存中获取原始资源
  • SourceGenerator:从数据源(例如网络)中获取资源

具体从哪一个生成器中获取资源跟用户的使用配置有关,没有配置时默认是Source
因此上面的currentGeneratorSourceGenerator

private DataFetcherGenerator getNextGenerator() {switch (stage) {case RESOURCE_CACHE:return new ResourceCacheGenerator(decodeHelper, this);case DATA_CACHE:return new DataCacheGenerator(decodeHelper, this);case SOURCE:return new SourceGenerator(decodeHelper, this);case FINISHED:return null;default:throw new IllegalStateException("Unrecognized stage: " + stage);}}

继续深入会发现在runGenerators方法中会调用currentGeneratorstartNext方法,很明显是SourceGenenrator的startNext方法。

@Overridepublic boolean startNext() {······loadData = null;boolean started = false;while (!started && hasNextModelLoader()) {//1.注意getLoadDataloadData = helper.getLoadData().get(loadDataListIndex++);if (loadData != null&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {started = true;startNextLoad(loadData);}}return started;}

重点关注getLoadData方法,这个方法中调用了modelLoader.buildLoadData,返回一个LoadData,它是工厂接口ModelLoader中的一个内部类。不必深究这个接口为什么设计,主要知道它是返回的它的实现子类HttpGlideUrlLoader,这时在初始化Glide是动态注册的

@Overridepublic LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time// spent parsing urls.GlideUrl url = model;if (modelCache != null) {url = modelCache.get(model, 0, 0);if (url == null) {modelCache.put(model, 0, 0, model);url = model;}}int timeout = options.get(TIMEOUT);return new LoadData<>(url, new HttpUrlFetcher(url, timeout));}

以下截图来自Glide类中
image.png

HttpGlideUrlLoader

既然如此自然而然调用的就是HttpGlideUrlLoader类中的buildLoadData方法

@Overridepublic LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {······int timeout = options.get(TIMEOUT);return new LoadData<>(url, new HttpUrlFetcher(url, timeout));}

可以看到最终又创建了一个HttpUrlFetcher,往这个类中深入

HttpUrlFetcher

来到HttpUrlFetcher中,可算是柳暗花明,终于看到了网络请求,最终返回的是一个InputStream对象。
而网络请求的实现原理是使用Android中的HttpURLConnection,但是从Android 4.4开始,HttpURLConnection的实现确实是通过调用okhttp完成的,而具体的方法则是通过HttpHandler这个桥梁,以及在OkHttpClient, HttpEngine中增加相应的方法来实现,当然,其实还涉及一些类的增加或删除。所以想研究网络请求的实现不妨再看以下OkHttp这个网络请求框架。

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {······//1.网络请求urlConnection = connectionFactory.build(url);for (Map.Entry<String, String> headerEntry : headers.entrySet()) {urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());}urlConnection.setConnectTimeout(timeout);urlConnection.setReadTimeout(timeout);urlConnection.setUseCaches(false);urlConnection.setDoInput(true);urlConnection.setInstanceFollowRedirects(false);urlConnection.connect();stream = urlConnection.getInputStream();if (isCancelled) {return null;}final int statusCode = urlConnection.getResponseCode();if (isHttpOk(statusCode)) {return getStreamForSuccessfulRequest(urlConnection);} else if (isHttpRedirect(statusCode)) {······}

DecedeJob

拿到了请求的结果,我们并不能直接显示这张图片,还需要进行采样压缩,否则很容易出现大图OOM。
多级回调最终回到了DecedeJob类中。
在runLoadPath方法中,又继续深入到了LoadPath类中执行采样压缩(这里就不再深入分析是怎么采样压缩的了,目的很明显,最终返回的就是一张被压缩优化的Bitmap图片)

private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path)throws GlideException {Options options = getOptionsWithHardwareConfig(dataSource);DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);try {// ResourceType in DecodeCallback below is required for compilation to work with gradle.return path.load(rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));} finally {rewinder.cleanup();}}

PathLoad

采样压缩的细节不再深入,最终返回的是一张优化处理后的Bitmap位图
最终将图片一系列的回调,回到了Engine类中

Engine

回调到EngineJob中,再回调到Engine中的onEngineJobComplete,顾名思义就是到是异步请求任务已经完成

@Override
public synchronized void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {if (resource != null && resource.isMemoryCacheable()) {activeResources.activate(key, resource);}jobs.removeIfCurrent(key, engineJob);
}

调用activate方法将它放入到活动缓存中,注意这是一个弱引用

synchronized void activate(Key key, EngineResource<?> resource) {ResourceWeakReference toPut =new ResourceWeakReference(key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);ResourceWeakReference removed = activeEngineResources.put(key, toPut);if (removed != null) {removed.reset();}}

SingleRequest

将图片保存到活动缓存中后,再由EngineJob回调到SingeRequest中的onResourceReady,意为资源已经准备好了

@GuardedBy("requestLock")private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {······try {······if (!anyListenerHandledUpdatingTarget) {//1.加载动画Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);//2.返回图片和动画target.onResourceReady(result, animation);}} finally {isCallingCallbacks = false;}//3.通知加载成功notifyLoadSuccess();}

ImageViewTarget

最终回到起点,设置图片,调用的是ImageViewTarget中的setResourceInternal方法,最终调用其中的抽象方法setResource

@Overridepublic void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {//1.设置图片资源if (transition == null || !transition.transition(resource, this)) {setResourceInternal(resource);} else {maybeUpdateAnimatable(resource);}}

有3个子类实现了这个方法,最终由它们去显示图片
image.png

DrawableImageViewTarget

这里的view就是一个ImageView对象,至此一张图片就显示在了屏幕上

@Overrideprotected void setResource(@Nullable Drawable resource) {view.setImageDrawable(resource);}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏洞复现-jquery-picture-cut 任意文件上传_(CVE-2018-9208)

jquery-picture-cut 任意文件上传_&#xff08;CVE-2018-9208&#xff09; 漏洞信息 jQuery Picture Cut v1.1以下版本中存在安全漏洞CVE-2018-9208文件上传漏洞 描述 ​ picture cut是一个jquery插件&#xff0c;以友好和简单的方式处理图像&#xff0c;具有基于bootstrap…

力扣每日一题79:单词搜索

题目描述&#xff1a; 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格…

【1++的Linux】之进程间通信(共享内存)

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 我们在前面的文章中提到过&#xff0c;进程间的通信本质都是先看到同一块资源&#xff0c;然后通过这同一块资源进行通信&#xff0c;并且是单向的通信&#xff0c;只能一端发&#…

Openssl数据安全传输平台011:秘钥协商客户端

文章目录 0. 代码仓库拷贝jsoncpp库至工程目录下编译protobuf类文件Message.proto VS 2022 设置 0. 代码仓库 https://github.com/Chufeng-Jiang/OpenSSL_Secure_Data_Transmission_Platform/tree/main/Preparation 拷贝jsoncpp库至工程目录下 编译protobuf类文件 VS2022 pr…

宝塔面板安装Python和Flask(新版Python项目)

&#xff08;一&#xff09;宝塔面板的项目菜单&#xff0c;打开Python项目的“项目版本管理” 安装Python版本3.10.0。 会创建一个Python版本的文件夹www/server/pyproject_evn/versions/ 会创建一个Python虚拟环境的文件夹www/server/pyproject_evn/python_venv/ &#xf…

线程安全问题

线程安全 简单来说&#xff0c;在多个线程访问某个方法或者对象的时候&#xff0c;不管通过任何的方式调用以及线程如何去交替执行。在程序中不做任何同步干预操作的情况下&#xff0c;这个方法或者对象的执行/修改都能按照预期的结果来反馈&#xff0c;那么这个类就是线程安全…

Cross-modal Variational Alignment of Latent Spaces

方法 潜空间LS 辅助信息 作者未公布代码

【数据结构】插入排序

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈数据结构 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 直接插入、希尔排序 1. 什么是排序2…

GPT做SQL查询引擎的自然语言

目录 面向企业查询的生成式人工智能 步骤1&#xff1a;将示例数据转换为单字符字符串 步骤2&#xff1a;为大型语言模型&#xff08;LM&#xff09;创建提示符 步骤3&#xff1a;将数据发送到OpenAI的API 步骤4&#xff1a;执行GPT返回的SQL代码的结果 步骤5(可选)&#…

shell算数运算指令、

1.shell算数运算的指令 (( )) $[ ] let expr expr的字符串运算 例子&#xff1a; 2.shell的if分支结构

第15届蓝桥杯Scratch选拔赛中级(STEMA)真题2023年8月

第15届蓝桥杯Scratch选拔赛中级&#xff08;STEMA&#xff09;真题2023年8月 一、单选题 第 1 题 单选题 点击以下积木块&#xff0c;生成的随机数是一个&#xff08; &#xff09;。 A.整数 B.小数 C.整数或小数 D.以上都不对 第 2 题 单选题 运行以下程序&#xff0…

【Docker】github Actions自动构建

通过github的Actions 实现代码push仓库后自动构建容器并发布到DockerHub. 创建项目 首先我们创建一个项目,这里我就用Vue项目进行演示. npm init vuelatest Actions-demo-swback进去项目&#xff0c;按照提示执行 npm install npm run dev 启动项目. 首先保证项目的正常启动…

数据库的概念和sql语句

数据&#xff1a;数字信息 据&#xff1a;就是属性 对一系列对象的具体属性的描述的集合 数据库&#xff1a;数据库就是用来组织&#xff08;各个数据之间是有关联。是按照规则组织起来的&#xff09;&#xff0c;存储和管理&#xff08;对数据的增删改查&#xff09;的仓库 …

Android 和 iOS APP 测试的那些区别

目前市面上主流的移动操作系统就是 Android 和 iOS 两种&#xff0c;移动端测试本身就跟 Web 应用测试有自己的专项测试&#xff0c;比如安装、卸载、升级、消息推送、网络类型测试、弱网测试、中断测试、兼容性测试等都是区别于 Web 应用需要关注的测试领域。 那么&#xff0…

汽车行驶性能的主观评价方法(1)-底盘校准方法

底盘校准的目的是&#xff0c;从行驶性能和行驶舒适性两个方面进行协调&#xff0c;从而优化行驶动力学特性。为了达到这一目标&#xff0c;工程人员早在设计阶段&#xff0c;就对大多数对行驶动力性有重要意义的部件提出了要求。这些要求不仅与底盘的组件有关&#xff0c;还必…

零资源的大语言模型幻觉预防

零资源的大语言模型幻觉预防 摘要1 引言2 相关工作2.1 幻觉检测和纠正方法2.2 幻觉检测数据集 3 方法论3.1 概念提取3.2 概念猜测3.2.1 概念解释3.2.2 概念推理 3.3 聚合3.3.1 概念频率分数3.3.2 加权聚合 4 实验5 总结 摘要 大语言模型&#xff08;LLMs&#xff09;在各个领域…

796. 子矩阵的和(二维前缀和)

题目&#xff1a; 796. 子矩阵的和 - AcWing题库 思路&#xff1a; 1.暴力搜索&#xff08;搜索时间复杂度为O(n2)&#xff0c;很多时候会超时&#xff09; 2. 前缀和&#xff08;左上角&#xff08;二维&#xff09;前缀和&#xff09;&#xff1a;本题特殊在不是直接求前…

730. 机器人跳跃问题--二分

题目&#xff1a; 730. 机器人跳跃问题 - AcWing题库 思路&#xff1a; 二分 1.当起始能量E大于最大建筑高度1e5 时&#xff0c;E的能量在整个条约过程中全程递增&#xff0c;则大于E的初始能量也必然成立&#xff08;满足二段性&#xff09;。故最小初始能量范围为[0,1e5]&a…

研发效能(DevOps)职业技术认证-第六期开班啦丨IDCF

本证书是由国家工业和信息化部教育与考试中心颁发的职业技术证书&#xff0c;也是国内首个《研发效能&#xff08;DevOps&#xff09;工程师职业技术认证》。该《认证》对研发效能&#xff08;DevOps&#xff09;工程师的职业技术分为初级、中级、高级三个专业等级。 IDCF社区…

[SQL开发笔记]UPDATE 语句:更新表中的记录

一、功能描述&#xff1a; UPDATE 语句&#xff1a;用于更新表中的记录 二、UPDATE 语句语法详解&#xff1a; UPDATE 语法 UPDATE table_nameSET column1value1,column2value2,...WHERE some_columnsome_value; 参数说明&#xff1a; 1.table_name&#xff1a;要修改的表…