Glide 缓存机制研究及同命名图片的替换回显

背景:

相册-图片后处理场景,需要先展示一张原图,同时后台对图片进行算法优化,完成优化之后无缝替换原图展示,同时保证后续都展示算法优化后的图片

图片加载采用 Glide 库实现

画重点:

  1. 相册场景:意味着存在图片缓存的复用

  2. 无缝替换:切换过程要很丝滑,不能有闪烁等现象

  3. 后续展示优化后的图片:说明后续缓存要复用算法优化后的,不能用原图

  4. 因为是对图片进行优化,所以原图和算法优化后的图片文件名一样

最开始的想法:

步骤:

  1. 先加载原图展示

  2. 等图片算法优化,重新调用 glide 加载一遍

结果:

  1. 重新加载之后,还是原图效果

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());
}

至此,效果实现!

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

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

相关文章

编程之道:程序员必备的五大职业素养

引言 在数字化时代&#xff0c;程序员的角色变得日益重要。他们不仅是代码的编写者&#xff0c;更是技术变革的推动者。然而&#xff0c;成为一名优秀的程序员&#xff0c;除了技术能力之外&#xff0c;还需要具备一系列职业素养。本文将探讨程序员在职业生涯中应具备的五大职业…

pycharm git配置

PyCharm 是一个强大的集成开发环境(IDE),它内置了 Git 集成,使得版本控制变得非常方便。以下是 PyCharm 中配置 Git 的基本步骤: 安装Git: 在开始之前,请确保已经在您的系统上安装了 Git。您可以通过官方网站下载并安装 Git。本系统用的是Git-2.29.2.2-64-bit 。 打开PyC…

24年下半年安徽教资认定准确时间和流程

安徽教资认定准确时间 网上报名时间&#xff1a; 第一批次&#xff1a;4月8日至4月19日17时 第二批次&#xff1a;6月17日至6月28日17时 注意&#xff1a;符合安徽省申请条件的普通大中专院校2024届全日制毕业生&#xff0c;应统一选择6月17日至6月28日17时的时间段进行网上报名…

vivado在implementation时出现错误[Place 30-494] The design is empty的一个可能原因和解决方法

在查询类似帖子时我发现这一问题是由于在设计实现时vivado认为没有输出端口所以报错。 于是在.v文件中我添加了一个随意的端口&#xff0c;并且在.xdc文件中为它分配了管脚 这样做的确可以让设计实现的过程顺利进行&#xff0c;但是会发现在summary中&#xff0c;设计实现的…

2024年6月17日 (周一) 叶子游戏新闻

期刊杂志: 聚合读者、意林、知音、故事会、花火以及国内各大知名报纸电子版&#xff0c;无需付费即可观看各种免费资源 中医自学宝典: 集合了中医医案&#xff0c;医经&#xff0c;方剂 药材知识的app&#xff0c;更方便的免费学习中医知识 《最终幻想7》重制三部曲总监鸟山求&…

每日复盘-202406017

今日关注&#xff1a; 20240617 六日涨幅最大: ------1--------301036--------- 双乐股份 五日涨幅最大: ------1--------301176--------- 逸豪新材 四日涨幅最大: ------1--------300868--------- 杰美特 三日涨幅最大: ------1--------301082--------- 久盛电气 二日涨幅最大…

阿里巴巴全球数学竞赛

都是很贴近生活的数学题&#xff0c;你来试试&#xff1f; 数学到底有没有用&#xff1f;华人数学家张益唐&#xff1a;数学很重要&#xff01; 在学校里辛辛苦苦学了那么多年&#xff0c;毕业后发现并不实用。学好数学到底有没有用&#xff1f; 昨天上午&#xff0c;华人数学家…

springboot+vue+mybatis酒店房间管理系统+PPT+论文+讲解+售后

随着现在网络的快速发展&#xff0c;网络的应用在各行各业当中它很快融入到了许多商家的眼球之中&#xff0c;他们利用网络来做这个电商的服务&#xff0c;随之就产生了“酒店房间管理系统”&#xff0c;这样就让人们酒店房间管理系统更加方便简单。 对于本酒店房间管理系统的…

BRAVE:扩展视觉编码能力,推动视觉-语言模型发展

视觉-语言模型&#xff08;VLMs&#xff09;在理解和生成涉及视觉与文本的任务上取得了显著进展&#xff0c;它们在理解和生成结合视觉与文本信息的任务中扮演着重要角色。然而&#xff0c;这些模型的性能往往受限于其视觉编码器的能力。例如&#xff0c;现有的一些模型可能对某…

车企高管组团“出道”,汽车营销已经Next level了?

汽车进入了“卷”老板、“卷”高管的时代&#xff01; 谁能想到&#xff0c;雷军凭一己之力&#xff0c;在一定程度上重塑了汽车的竞争策略。价格战之外&#xff0c;车市又开启了流量之战。 云略曾在《雷军20天吸粉500w&#xff01;……》一文中&#xff0c;提到继雷军之后&…

爆肝整理AI Agent:在企业应用中的6种基础类型

AI Agent智能体在企业应用中落地的价值、场景、成熟度做了分析&#xff0c;并且探讨了未来企业IT基础设施与架构如何为未来Gen AI&#xff08;生成式AI&#xff09;做好准备。在这样的架构中&#xff0c;我们把最终体现上层应用能力的AI Agent从不同的技术要求与原理上分成了几…

【MySQL】分库分表

https://www.bilibili.com/video/BV1Kr4y1i7ru/?p163​ https://blog.csdn.net/qq_47959003/article/details/126058710 随着互联网及移动互联网的发展&#xff0c;应用系统的数据量也是成指数式增长&#xff0c;若采用单数据库进行数据存储&#xff0c;存在以下性能瓶颈&…

Java中线程的创建方式

一、继承Thread类&#xff0c;重写run方法 public class MyThread{public static void main(String[] args) {Thread threadDome new ThreadDome();threadDome.start();} }class ThreadDome extends Thread{Overridepublic void run() {for (int i 0; i < 5; i) {try {Th…

Java项目:基于SSM框架实现的汽车养护保养管理系统【ssm+B/S架构+源码+数据库+开题+毕业论文+任务书】

一、项目简介 本项目是一套基于SSM框架实现的汽车养护保养管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

Linux之逻辑控制符

Linux之逻辑控制符&& 用&&连接两个命令&#xff0c;前一个命令执行成功&#xff0c;才会执行后一个命令。&&此命令可以连接很多个 命令格式 command1 && command2 command1 && command2 && command3 command1 && co…

【机器学习】基于顺序到顺序Transformer机器翻译

引言 1.1 序列到序列模型详解 序列到序列(Seq2Seq)模型是深度学习中处理序列数据转换问题的关键架构。在自然语言处理(NLP)任务中&#xff0c;如机器翻译、文本摘要和聊天机器人等&#xff0c;Seq2Seq模型能够高效地将输入序列转换为期望的输出序列。 模型架构&#xff1a; 编…

宋街宣传活动-循环利用,绿色生活

善于善行回收团队是一支致力于推动环保事业&#xff0c;积极倡导和实践绿色生活的志愿者队伍。我们的宗旨是通过回收再利用&#xff0c;减少资源浪费&#xff0c;降低环境污染&#xff0c;同时提高公众的环保意识&#xff0c;共同构建美丽和谐的家园。 善于善行志愿团队于2024年…

工具清单 - 看板工具

# 工具清单 Crepido在新窗口打开 - Create (kanban) boards to track users and projects from flat markdown files. MIT NodejsKanboard在新窗口打开 - Simple and open source visual task board. (Source Code在新窗口打开) MIT PHPmyTinyTodo在新窗口打开 - Simple way t…

C语言数据存储大小端问题

大小端 什么是大小端 大端模式&#xff08;Big-endian&#xff09;&#xff0c;是指数据的高字节&#xff0c;保存在内存的低地址中&#xff0c;而数据的低字节&#xff0c;保存在内存的高地址中; 小端模式&#xff08;Little-endian&#xff09;&#xff0c;是指数据的高字…

函数递归

哈喽啊各位&#xff0c;真是&#xff0c;好久好久好久不见。这段时间实在是太过忙碌了昂&#xff0c;还望诸君见谅&#xff0c;接下来时间会松很多&#xff0c;咱们也会恢复正常更新速度啦 小希在这里祝诸君&#xff1a;期末不挂科&#xff0c;四六级都过&#xff01;功不唐捐…