Glide 缓存机制研究及同命名图片的替换回显
背景:
相册-图片后处理场景,需要先展示一张原图,同时后台对图片进行算法优化,完成优化之后无缝替换原图展示,同时保证后续都展示算法优化后的图片
图片加载采用 Glide 库实现
画重点:
-
相册场景:意味着存在图片缓存的复用
-
无缝替换:切换过程要很丝滑,不能有闪烁等现象
-
后续展示优化后的图片:说明后续缓存要复用算法优化后的,不能用原图
-
因为是对图片进行优化,所以原图和算法优化后的图片文件名一样
最开始的想法:
步骤:
-
先加载原图展示
-
等图片算法优化,重新调用 glide 加载一遍
结果:
-
重新加载之后,还是原图效果
Glide 缓存机制
基于上述原因,专门研究了下 Glide 的缓存机制,重点是缓存的读取这一块
四级缓存机制
Glide 采用四级缓存机制,主要分成两个模块,内存缓存,硬盘缓存
- 内存缓存:防止应用重复将图片数据读到内存中
- 硬盘缓存:防止应用重复从网络或者其他地方重复下载和读取数据
- 缓存Key:既然有缓存,那肯定需要对应的缓存Key,通过按照一定规则生成缓存key之后,根据key从缓存集中取出对应缓存复用
这里先简单讲下四级缓存机制,然后再跟着源码过一遍
内存缓存-lru缓存:
Glide 在内存中维持的一个缓存 map,通过 lru 算法实现内存管理
private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true);
T:缓存Key
Y:缓存资源
内存缓存-弱引用缓存:
Glide 在内存中维持的一个弱引用缓存 map,由于是弱引用,这就意味着内存不足的时候先销毁这个
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
硬盘缓存-转换过缓存:
Glide 在硬盘上的缓存,该缓存是经过各种变换后的缓存,不是原始图片
硬盘缓存-原始图片缓存:
Glide 在硬盘上的缓存,该缓存是原始图片缓存
Glide 缓存加载流程:
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {Util.assertMainThread();long startTime = LogTime.getLogTime();
final String id = fetcher.getId();// 1. 生成 缓存key,可以看出 key 的生成用到很多参数,比如 id(图片路径)、signature、width、height,还有 transformationEngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());
// 2. 先从内存缓存-lru缓存中拿缓存,如果拿到缓存调用 onResourceReadyEngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;}// 3. 内存缓存-lru中没有缓存,则从弱引用缓存中拿,如果拿到调用 onResourceReadyEngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {cb.onResourceReady(active);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;}// 4. 内存缓存-弱引用中也没有缓存,从硬盘缓存中拿或加载文件地址,不管是拿硬盘缓存还是加载文件,涉及到IO操作,需要子线程处理EngineJob current = jobs.get(key);if (current != null) {current.addCallback(cb);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,transcoder, diskCacheProvider, diskCacheStrategy, priority);EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(runnable);
if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);
}
内存缓存-lru缓存中拿缓存:
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}// 如果从 lru 中能拿到缓存,将该缓存从 lru 缓存中移除,并存入 弱引用缓存EngineResource<?> cached = getEngineResourceFromCache(key);if (cached != null) {cached.acquire();activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));}return cached;
}
从硬盘缓存中拿或加载文件地址:
private Resource<?> decode() throws Exception {// 这里有个策略判断,只有支持硬盘缓存策略,才能从硬盘中拿缓存if (isDecodingFromCache()) {// 从硬盘缓存中拿return decodeFromCache();} else {// 加载文件return decodeFromSource();}
}
// 从硬盘缓存中拿
private Resource<?> decodeFromCache() throws Exception {Resource<?> result = null;try {// 5. 从硬盘缓存-转换过缓存中拿result = decodeJob.decodeResultFromCache();} catch (Exception e) {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Exception decoding result from cache: " + e);}}
if (result == null) {// 6. 硬盘缓存-转换过缓存中拿不到,从原始图片缓存中拿result = decodeJob.decodeSourceFromCache();}return result;
}
// 从硬盘缓存-转换过缓存中拿
public Resource<Z> decodeResultFromCache() throws Exception {if (!diskCacheStrategy.cacheResult()) {return null;}
long startTime = LogTime.getLogTime();Resource<T> transformed = loadFromCache(resultKey);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded transformed from cache", startTime);}startTime = LogTime.getLogTime();Resource<Z> result = transcode(transformed);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transcoded transformed from cache", startTime);}return result;
}
// 从硬盘缓存-原始图片缓存中拿
public Resource<Z> decodeSourceFromCache() throws Exception {if (!diskCacheStrategy.cacheSource()) {return null;}
long startTime = LogTime.getLogTime();// 这里值得注意的是,这里用的key和其他三个缓存中用到的不一样,这里用的是 resultKey.getOriginalKey() 就是原始图片地址,不过也能理解,加载原始图片缓存,也不需要额外的key信息,只需要文件地址就行了Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded source from cache", startTime);}return transformEncodeAndTranscode(decoded);
}
理完Glide的加载机制之后,就很好理解为什么重新加载之后还是原图,所以进行了优化
优化方案二
加载原图时:
glideOptions = new RequestOptions().signature(new ObjectKey(signatureKey));
加载优化后图片时:
glideOptions = new RequestOptions();
加载原图时多调用了 signature 方法,这样加载优化后图片时,由于和加载原图时的缓存key不一样,会重新进行加载
优化方案二解决了优化图片不加载问题,但是由于重新加载图片,会导致切换过程出现很明显的闪烁,这里可以通过 placeholder 方法优化
优化方案三
// 1. 获取当前 imageView 的 drawable
Drawable drawable = imageView.getDrawable();
Bitmap placeholderBitmap = null;
// 2. drawable 转成 bitmap
if(drawable instanceof BitmapDrawable){placeholderBitmap = ((BitmapDrawable) drawable).getBitmap();
}
// 3. 由于 imageView 的 drawable 可能会被回收,所以需要将 bitmap 重新 clone 一份
Bitmap copiedBitmap = null;
if(placeholderBitmap != null){copiedBitmap = placeholderBitmap.copy(placeholderBitmap.getConfig(), true);
}
// 4. 将 bitmap 作为 占位符传入
if(copiedBitmap != null && !copiedBitmap.isRecycled()){glideOptions = new RequestOptions().frame(0).placeholder(new BitmapDrawable(context.getResources(), copiedBitmap)).override(size.getWidth(), size.getHeight());
}else {glideOptions = new RequestOptions().frame(0).override(size.getWidth(), size.getHeight());
}
至此,效果实现!
相关文章:
Glide 缓存机制研究及同命名图片的替换回显
背景: 相册-图片后处理场景,需要先展示一张原图,同时后台对图片进行算法优化,完成优化之后无缝替换原图展示,同时保证后续都展示算法优化后的图片 图片加载采用 Glide 库实现 画重点: 相册场景ÿ…...

一键简易桌签(带背景)-Word插件-大珩助手
问题整理: 如何Word中设计简易桌签?如何设置带背景图的桌签? Word大珩助手是一款功能丰富的Office Word插件,旨在提高用户在处理文档时的效率。它具有多种实用的功能,能够帮助用户轻松修改、优化和管理Word文件&…...
如何解决centos停止维护后更换Debian11平替?
再有9天时间centos7.x就将停止维护,最近很多人已经再更换系统了!有推荐alibabacloud,opencloud,debian,ubuntu,centos-stream,tencentos,anolis,rockey,almal,oracle,arch,Fedora,opensuse 很多人不知道选什么,这里就主要介绍一下 如果你是使用宝塔面板 并且想要无缝衔…...
服务器雪崩的应对策略之----熔断机制
熔断机制(Circuit Breaker)是一种保护系统稳定性的重要手段。它的主要目的是防止系统在依赖的服务出现问题时,继续发送请求,从而保护系统免受进一步的影响。熔断机制通过监控请求的成功和失败率,在检测到故障率超过预设…...

RestClient操作索引库和文档
导入依赖: <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.12.1</version></dependency>导入依赖成功之后就可以开始我们的测…...

【设计模式深度剖析】【11】【行为型】【解释器模式】| 以算术表达式求值为例加深理解
👈️上一篇:状态模式 设计模式-专栏👈️ 文章目录 解释器模式定义英文原话直译 解释器模式中的角色1. 抽象表达式(AbstractExpression)2. 终端表达式(TerminalExpression)3. 非终端表达式(Non…...

MySQL8,Navicat能登陆成功,密码却忘记了
执行成功的图: 以下为步骤:本文一共8个简单步骤。 环境:mysql8、window10、navicat11 1、打开本地电脑window10的命令窗(俗称黑窗口),windowR 2、输入regegit,回车,打开注册表 3、…...
游戏中的寻路算法研究
1)Unity NavMesh研究 思考:毫无疑问,unity中自带的navmesh寻路是比较健全的,无奈在服务器中无法使用,这样子我们没办法做怪的导航,但是可以先体验下都有哪些api,并且可以可视化的看效果。 1)打开导航网格…...

【AWS SMB】关于AWS 中小型企业 (SMB) 能力介绍及注意事项
文章目录 前言一、什么是 SMB?📢二、如何识别中小企业的需求三、中小企业营销活动的类型四、AWS 合作伙伴可获得的其他 AWS 机会4.1 AWS IQ4.2 APN 客户参与 (ACE) 计划 前言 AWS 中小型企业 (SMB) 能力合作伙伴专注于帮助中小型…...
中年之恋:重返青春的旅程
第一章:重逢 在一个普通的周末,李明参加了一次由老同学组织的聚会,尽管他对此并不抱有太大的兴趣,但出于礼貌还是选择了出席。聚会在一家风格复古的咖啡馆里举行,暖黄色的灯光与木质的装饰让人不自觉地放缓了脚步。在…...

人工智能中的监督学习和无监督学习
欢迎来到 Papicatch的博客 目录 🍉引言 🍉监督学习 🍈基本思想 🍈具体过程 🍍数据收集 🍍数据预处理 🍍模型选择 🍍模型训练 🍍模型评估 🍍模型部署…...
深度学习500问——Chapter12:网络搭建及训练(1)
文章目录 12.1 TensorFlow 12.1.1 TensorFlow 是什么 12.1.2 TensorFlow的设计理念是什么 12.1.3 TensorFlow特点有哪些 12.1.4 TensorFlow的系统架构是怎样的 12.1.5 TensorFlow编程模型是怎样的 12.1.6 如何基于TensorFlow搭建VGG16 12.1 TensorFlow 12.1.1 TensorFlow 是什…...
HuggingFace CLI 命令全面指南
文章目录 安装与认证1.1 安装 HuggingFace Hub 库使用 pip 安装使用 conda 安装验证安装 1.2 认证与登录生成访问令牌使用访问令牌登录环境变量认证验证认证 下载文件2.1 下载单个文件安装 huggingface_hub 库认证与登录下载单个文件 2.2 下载特定版本的文件下载特定版本的文件…...

FreeRTOS源码分析
目录 1、FreeRTOS目录结构 2、核心文件 3、移植时涉及的文件 4、头文件相关 4.1 头文件目录 4.2 头文件 5、内存管理 6、入口函数 7、数据类型和编程规范 7.1 数据类型 7.2 变量名 7.3 函数名 7.4 宏的名 1、FreeRTOS目录结构 使用 STM32CubeMX 创建的 FreeRTOS 工…...
python实战:将视频内容上传到社交媒体平台
在Python中,上传视频到不同的平台可能需要使用不同的API和库。以下是一些常见的平台以及如何使用Python进行上传的示例: YouTube: 使用Google提供的YouTube Data API。 首先,你需要从Google Cloud控制台获取API密钥,并安装google-…...

【深度学习】sdwebui A1111 加速方案对比,xformers vs Flash Attention 2
文章目录 资料支撑资料结论sdwebui A1111 速度对比测试sdxlxformers 用contorlnet sdxlsdpa(--opt-sdp-no-mem-attention) 用contorlnet sdxlsdpa(--opt-sdp-attention) 用contorlnet sdxl不用xformers或者sdpa ,用contorlnet sdxl不用xformers或者sdpa …...
5分钟了解单元测试
🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 一、什么是单元测试? 单元测试是指,对软件中的最小可测试单元在与程序其…...

VSCode之C/C++插件之宏定义导致颜色变暗
这是因为该宏没有定义或者定义在makefile文件中导致无法被插件识别到,导致误判了 索性将该机制去了,显示也会好看些,如下将C_Cpp下的Dim Inactive Regions勾去了 显示效果会好很多。...

自然语言处理概述
目录 1.概述 2.背景 3.作用 4.优缺点 4.1.优点 4.2.缺点 5.应用场景 5.1.十个应用场景 5.2.文本分类 5.2.1.一般流程 5.2.2.示例 6.使用示例 7.总结 1.概述 自然语言处理(NLP)是计算机科学、人工智能和语言学的交叉领域,旨在实…...
用Rust和Pingora轻松构建超越Nginx的高效负载均衡器
目录 什么是Pingora?实现过程 初始化项目编写负载均衡器代码代码解析部署 总结 1. 什么是Pingora? Pingora 是一个高性能的 Rust 库,用于构建可负载均衡器的代理服务器,它的诞生是为了弥补 Nginx 存在的缺陷。 Pingora 提供了…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...