Glide 的超时控制相关处理

作者:newki

前言

Glide 相信大家都不陌生,各种源码分析,使用介绍大家应该都是烂熟于心。但是设置 Glide 的超时问题大家遇到过没有。

我遇到了,并且掉坑里了,情况是这样的。

  1. 调用接口从网络拉取用户头像,目前数据量不大,大致1000多个人。(用了自定义队列)
  2. 使用 Glide 下载头像到本地沙盒 File (为了方便的缓存下次更快)。
  3. 识别头像中的人脸信息,并生成人脸Bitmap,(本身有成功失败的处理与重试机制)
  4. 生成人脸对应的特征,并保存人脸特征数据和人脸特征图片到沙盒 File 。
  5. 封装人脸对象并加载到内存中保持全局单例。
  6. 场景业务:与Camera的预览画面中获取到的活体人脸进行人脸比对。

开始并没有设置超时时间,导致 Glide下载图片的自定义队列常常会出现卡死的情况,导致整个队列执行缓慢甚至都无法继续执行,整个注册服务被阻塞,新进来的用户一直等待时间过长甚至无法注册。

问题嘛,就是图片加载的问题,有些图片无法加载,有些图片太大加载时间过长,有些根本就不是图片,有些网络慢,不稳定,或者干脆就无网,有些是访问权限问题,为了让图片下载队列能正常运转加入了 Glide 的超时机制,踩坑之路由此展开。

一、问题复现

Glide的使用,大家应该都清除,如何加timeout,这里给出一个示例代码:

依赖:

implementation 'com.github.bumptech.glide:glide:4.15.1'
implementation 'com.github.bumptech.glide:annotations:4.15.1'
kapt 'com.github.bumptech.glide:compiler:4.15.1'

下载的方法使用一个扩展方法封装了一下 :

fun Any.extDownloadImage(context: Context?, path: Any?, block: (file: File) -> Unit) {var startMillis = 0Lvar endMillis = 0LGlideApp.with(context!!).load(path).timeout(15000)  // 15秒.downloadOnly(object : SimpleTarget<File?>() {override fun onLoadStarted(placeholder: Drawable?) {startMillis = System.currentTimeMillis()YYLogUtils.w("开始加载:$startMillis")super.onLoadStarted(placeholder)}override fun onLoadFailed(errorDrawable: Drawable?) {endMillis = System.currentTimeMillis()YYLogUtils.w("Glide-onLoadFailed-Drawable,一共耗时:${endMillis - startMillis}")super.onLoadFailed(errorDrawable)}override fun onResourceReady(resource: File, transition: Transition<in File?>?) {endMillis = System.currentTimeMillis()YYLogUtils.w("Glide-onResourceReady-Drawable,一共耗时:${endMillis - startMillis}")block(resource)}})}

大家使用工具类或者直接 Glide 写都是一样的效果,不影响最终的结果。

使用:

val url = "https://s3.ap-southeast-1.amazonaws.com/yycircle-ap/202307/11/KZ8xIVsrlrYtjhw3t2t2RTUj0ZTWUFr2EhawOd4I-810x1080.jpeg"extDownloadImage(this@MainActivity, url, block = { file ->YYLogUtils.w("file:${file.absolutePath}")})

以亚马逊云服务的图片地址为例,不同的网络情况,不同的网络加载框架情况下,分别有什么不同。

1.1 HttpURLConnection 没网的情况

原生 Glide 的网络请求源码在 HttpUrlFetcher 类中。

具体方法:

就算我们在 buildAndConfigureConnection 中设置了超时时间,但是 connect 方法直接就报错了,也不会走timeout的逻辑

com.bumptech.glide.load.HttpException: Failed to connect or obtain data, status code: -1

1.1 HttpURLConnection 有网的但是不通

那如果有网,但是网不通呢?

这下确实会等待一小会了,由于我们设置的超时时间是15秒,打印Log看看。

class com.bumptech.glide.load.HttpException: Failed to connect or obtain data, status code: -1

错误和上面一样,但是超时时间是10秒:

喂,玩我是吧。那我改 Glide 的超时时间为 5000, 也就是5秒,但是最终的结果还是10秒。

这是为什么呢?虽然连上了WIFI,但是没网,还是无法解析hostname,而 HttpURLConnection 内部定义的这一阶段的超时就是 10 秒。

我们可以把 Glide 的网络请求源码拷过来试试!

class HttpTest {private final HttpUrlConnectionFactory connectionFactory = new DefaultHttpUrlConnectionFactory();public HttpTest() {}public HttpURLConnection buildAndConfigureConnection(URL url, Map<String, String> headers) throws HttpException {HttpURLConnection urlConnection;try {urlConnection = connectionFactory.build(url);} catch (IOException e) {throw new RuntimeException("URL.openConnection threw");}for (Map.Entry<String, String> headerEntry : headers.entrySet()) {urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());}urlConnection.setConnectTimeout(7000);urlConnection.setReadTimeout(7000);urlConnection.setUseCaches(false);urlConnection.setDoInput(true);urlConnection.setInstanceFollowRedirects(false);return urlConnection;}interface HttpUrlConnectionFactory {HttpURLConnection build(URL url) throws IOException;}private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {DefaultHttpUrlConnectionFactory() {}@Overridepublic HttpURLConnection build(URL url) throws IOException {return (HttpURLConnection) url.openConnection();}}
}

为了和之前的区别开,我们设置7秒的超时,看看结果有什么变化?

java.net.UnknownHostException: Unable to resolve host “s3.ap-southeast-1.amazonaws.com”: No address associated with hostname

错误已经很明显了,哎

1.1 HttpURLConnection 有网通了,但是没访问权限

那我现在把网连上,把授权关掉,虽然能解析域名,但是没有访问权限,还是无法获取图片,此时又会出现什么情况。

我们还是设置为15秒的超时:

 GlideApp.with(context!!).load(path).apply(options).timeout(15000).into(object : SimpleTarget<Drawable>() {override fun onLoadStarted(placeholder: Drawable?) {startMillis = System.currentTimeMillis()YYLogUtils.w("开始加载:$startMillis")super.onLoadStarted(placeholder)}override fun onLoadFailed(errorDrawable: Drawable?) {endMillis = System.currentTimeMillis()YYLogUtils.w("Glide-onLoadFailed-Drawable,一共耗时:${endMillis - startMillis}")super.onLoadFailed(errorDrawable)}override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {endMillis = System.currentTimeMillis()YYLogUtils.w("Glide-onResourceReady-Drawable,一共耗时:${endMillis - startMillis}")block(resource)}})

出错的信息,这次网络请求确实是通了,确实是走到 timeout 里面了。

但是这个时间为什么是30秒?

如果我们设置超时时间是20秒?那么结果就是40秒!

是 HttpURLConnection 的问题?我们还是用上一步的 7秒超时的原生 HttpURLConnection 代码访问试试!

可以看到结果是符合我们预期的7秒超时。

那为什么 Glide 默认的 HttpURLConnection 会是两倍的超时时间呢?

是因为 Glide 内部对 HttpURLConnection 的请求做了重试处理。

当它第一次超时的时候,会走到错误回调中,但是并没有回调出去,而是自己处理了一遍。

真的太迷了,我自己不会学重试吗,要你多管闲事…

1.1 换成 OkHttp3

如果摆脱这一套 HttpURLConnection 的逻辑与重试逻辑,Glide 也提供了第三方网络请求的接口,例如我们常用的用 OkHttp 来加载图片。

大家应该是不陌生的,加入依赖库即可:

implementation 'com.github.bumptech.glide:okhttp3-integration:4.15.1'

此时已经换成OkHttp加载了,它默认的超时时间就是10秒,此时我们修改Glide的超时时间是无效的。

    GlideApp.with(context!!).load(path).apply(options).timeout(20000) .into(object : SimpleTarget<Drawable>() {override fun onLoadStarted(placeholder: Drawable?) {startMillis = System.currentTimeMillis()YYLogUtils.w("开始加载:$startMillis")super.onLoadStarted(placeholder)}override fun onLoadFailed(errorDrawable: Drawable?) {endMillis = System.currentTimeMillis()YYLogUtils.w("Glide-onLoadFailed-Drawable,一共耗时:${endMillis - startMillis}")super.onLoadFailed(errorDrawable)}override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {endMillis = System.currentTimeMillis()YYLogUtils.w("Glide-onResourceReady-Drawable,一共耗时:${endMillis - startMillis}")block(resource)}})

别说改成20秒,改成100秒也无效!因为这些配置是修改的默认的 HttpURLConnection 的超时时间的。OkHttp的加载根本就不走那一套了。

打印 Log 如下:

哎,真的是头都大了,不是说好的开箱即用吗,咋个这么多问题,还分这么多情况,真不知道该如何是好。

二、问题解决1,使用 OkHttp3 的自定义 Client

既然我们使用 OkHttp 之后,无法在 Glide 中修改超时时间,那么我们直接修改 OkHttp 的超时时间可不不可以?

大家或多或少都配置过,这里直接贴代码:

@GlideModule
public final class HttpGlideModule extends AppGlideModule {@Overridepublic void registerComponents(Context context, Glide glide, Registry registry) {// 替换自定义的Glide网络加载registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(GlideOkHttpUtils.getHttpClient()));}
}

实现我们自己的 OkHttpClient 类:

public class GlideOkHttpUtils {public static OkHttpClient getHttpClient() {OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).addInterceptor(new LoggingInterceptor())  //打印请求日志,可有可无.sslSocketFactory(getSSLSocketFactory()).hostnameVerifier(getHostnameVerifier());return builder.build();}/*** getSSLSocketFactory、getTrustManagers、getHostnameVerifier* 使OkHttpClient支持自签名证书,避免Glide加载不了Https图片*/private static SSLSocketFactory getSSLSocketFactory() {try {SSLContext sslContext = SSLContext.getInstance("SSL");sslContext.init(null, getTrustManagers(), new SecureRandom());return sslContext.getSocketFactory();} catch (Exception e) {throw new RuntimeException(e);}}private static TrustManager[] getTrustManagers() {return new TrustManager[]{new X509TrustManager() {@Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}@Overridepublic X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};}}};}private static HostnameVerifier getHostnameVerifier() {return new HostnameVerifier() {@Overridepublic boolean verify(String hostname, SSLSession session) {return true;}};}}

可以看到我们设置了15秒的超时,打印的结果如下:

想设置几秒就是几秒,没有重试导致时间不对一说。这确实是一种方案。

三、问题解决2,使用协程timeout

另一种方案就是使用协程的超时来控制,由于 Glide 的加载图片与回调的处理是匿名函数实现的,内部回调的处理我们先用协程处理铺平回调。

之前讲过,这里直接上代码

suspend fun Any.downloadImageWithGlide(imgUrl: String): File {return suspendCancellableCoroutine { cancellableContinuation ->GlideApp.with(commContext()).load(imgUrl).timeout(15000)  //设不设都一样,反正不靠你.diskCacheStrategy(DiskCacheStrategy.DATA).downloadOnly(object : SimpleTarget<File?>() {override fun onResourceReady(resource: File, transition: Transition<in File?>?) {cancellableContinuation.resume(resource)}override fun onLoadFailed(errorDrawable: Drawable?) {super.onLoadFailed(errorDrawable)cancellableContinuation.resumeWithException(RuntimeException("加载失败了"))}})}
}

使用起来我们就是协程的 timeout 函数,不管底层是什么实现的,直接上层的超时拦截。

    launch{...try {val file = withTimeout(15000) {downloadImageWithGlide(userInfo.avatarUrl)}YYLogUtils.e("注册人脸服务-图片加载成功:${file.absolutePath}")//下载成功之后赋值本地路径到对象中userInfo.avatarPath = file.absolutePath//去注册人脸registerHotelMember(userInfo)} catch (e: TimeoutCancellationException) {YYLogUtils.e("注册人脸服务-图片加载超时:${e.message}")checkImageDownloadError(userInfo)} catch (e: Exception) {YYLogUtils.e("注册人脸服务-图片加载错误:${e.message}")checkImageDownloadError(userInfo)}}

这也是比较方便的一种方案。

后记

如果是网络请求,不管是接口的Http或者是Glide的图片加载,我们可以使用OkHttp加载,可以设置 OkHttpClient 的 Timeout 属性来设置超时。

如果是其他的异步操作,我们也可以使用协程的 timeout 函数直接在上层超时取消协程,也能达到目的。

两种方法都是可以的,我个人是选择了协程 timeout 的方式,因为我发现有些情况下就算设置 OkHttp 的超时,偶尔还是会长时间超时。如网络连接较慢或不稳定,如服务端没有及时响应或响应时间过长,那么超时机制将无法起作用。所以为了保险起见还是使用协程 timeout 直接上层处理了,更新之后目前运行状况良好。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

在外SSH远程连接Ubuntu系统

在外SSH远程连接Ubuntu系统【无公网IP】 文章目录 在外SSH远程连接Ubuntu系统【无公网IP】前言1. 在Ubuntu系统下安装cpolar软件2. 完成安装后打开cpolar客户端web—UI界面3. 创建隧道取得连接Ubuntu系统公网地址4. 打开Windows的命令界面并输入命令 前言 随着科技和经济的发展…

解锁Python集合的妙用:常用函数与实例深度解析

Python的集合&#xff08;Set&#xff09;是一种无序且不重复的数据结构&#xff0c;拥有强大的去重和集合运算功能。在这篇博客中&#xff0c;我们将深入探讨集合的常用函数&#xff0c;并通过实际案例为你展示其灵活应用。 创建集合​ 集合可以通过花括号来创建&#xff0c…

golang拥有wireshark数据包解析能力

golang拥有wireshark数据包解析能力 1. 功能和实现 wireshark拥有世界上最全面的协议解析能力并且还在不断更新中&#xff0c;通过调研&#xff0c;没有办法找到与wireshark同水平的解析工具。 为了使得golang语言可以拥有wireshark一样强大的协议解析能力&#xff0c;库 gowir…

生态系统服务(InVEST模型)

第一天&#xff1a; 1. 生态系统服务理论联系实践案例讲解 2. InVEST模型的开发历程、不同版本的差异及对数据需求的讲解 3. InVEST所需数据的要求&#xff08;分辨率、格式、投影系统等&#xff09;、获取及标准化预处理讲解 4. InVEST运行常见问题及处理解决方法讲解 5.…

微信小程序实现当前页面更新上一个页面

日常项目中需要实现的一个价格脱敏功能&#xff1a;通过点击页面二中的查看完整信息 点击回退按钮实现页面一中的价格显露出来 通过查询了大量资料发现 大多数都是通过调用上一个接口的onload 或者onshow 实现视图更新 经测试后 发现 无法实现 只能更改数据 无法更新视图 实现…

解决:django设置DEBUG=false时出现的问题

首先&#xff0c;我用的是django4.2&#xff0c;python3.10版本 本来&#xff0c;如果在settings.py中使用 DEBUG True&#xff0c;那么什么问题也没有&#xff0c;当然&#xff0c;这属于调试模式。 DEBUG True TEMPLATE_DEBUG DEBUGSTATIC_URL /static/ STATICFILES_DI…

【C++】开源:ceres和g2o非线性优化库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍ceres和g2o非线性优化库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

大模型落地金融业,想象力在哪?

金融大模型的难点在于&#xff0c;能否在产业中扎得更深&#xff1b;其颠覆性也更建立在&#xff0c;纵深到产业中去&#xff0c;赋能金融行业的长尾场景发展&#xff0c;以及重拾“金融信任”。 作者|思杭 编辑|皮爷 出品|产业家 “从经济角度讲&#xff0c;整个金融业…

eNSP 实现 CLI 窗口叠放

文章目录 1 问题截图2 问题解决3 扩展3.1 打开所有 CLI3.2 CLI&#xff1a;Command line interface 1 问题截图 问题描述&#xff1a;命令行窗口是分开的&#xff0c;找对应的窗口太麻烦了 2 问题解决 解决办法&#xff1a;点下图控件即可。 效果展示&#xff1a; 3 扩展 …

16-3_Qt 5.9 C++开发指南_使用QStyle 设置界面外观_实现不同系统下的界面效果的匹配

文章目录 1. QStyle的作用&#xff08;实现不同系统下的界面效果的匹配&#xff09;2. Qt内置样式的使用3. 源码3.1 可视化UI设计3.2 mainwindow.cpp 1. QStyle的作用&#xff08;实现不同系统下的界面效果的匹配&#xff09; Qt 是一个跨平台的类库&#xff0c;相同的界面组件…

STM32 4G学习

硬件连接 ATK-IDM750C模块可直接与正点原子 MiniSTM32F103开发板板载的ATK模块接口&#xff08;ATK-MODULE&#xff09;进行连接。 功能说明 ATK-IDM750C是正点原子&#xff08;ALIENTEK&#xff09;团队开发的一款高性能4G Cat1 DTU产品&#xff0c;支持移动4G、联通4G和…

MySQL_事务学习笔记

事务 注意&#xff1a;一定要使用 Innodb 存储引擎 概述&#xff1a;一组操作的集合&#xff0c;是不可分割的工作单元&#xff0c;会把一个部分当成一个整体来处理&#xff0c;事务会把操作同时提交或者是撤销。要么同时成功&#xff0c;要么同时失败。 比如&#xff1a;上云…

LVS集群

目录 1、lvs简介&#xff1a; 2、lvs架构图&#xff1a; 3、 lvs的工作模式&#xff1a; 1&#xff09; VS/NAT&#xff1a; 即&#xff08;Virtual Server via Network Address Translation&#xff09; 2&#xff09;VS/TUN &#xff1a;即&#xff08;Virtual Server v…

SAP Fiori 将GUI中的自开发报表添加到Fiori 工作台

1. 首先我们在workbench 中开发一个GUI report 这里我们开发的是一个简单的物料清单报表 2. 分配一个事务代码。 注意这里的SAP GUI for HTML 要打上勾 3. 创建语义对象&#xff08; Create Semantic Object&#xff09; 事物代码&#xff1a; path: SAP NetWeaver ->…

如何写一篇吸引人的新闻稿?揭秘新闻稿写作的技巧!

一篇高质量的新闻稿不仅能够吸引读者的眼球&#xff0c;还能提高文章的曝光量。下面&#xff0c;伯乐网络传媒将给大家揭秘新闻稿写作的十大技巧&#xff0c;帮助大家写出更有吸引力的新闻稿。 1. 选择热门而有吸引力的话题或爆点 要想写出一篇吸引人的新闻稿&#xff0c;首先…

SQL | 使用函数处理数据

8-使用函数处理数据 8.1-函数 SQL可以用函数来处理数据。函数一般是在数据上执行的&#xff0c;为数据的转换和处理提供了方便。 8.1.1 函数带来的问题 每种DBMS都有特定的函数&#xff0c;只有很少一部分函数&#xff0c;是被所有主要的DBMS等同的支持。 虽然所有的类型的…

『赠书活动 | 第十七期』《Python网络爬虫:从入门到实战》

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 『赠书活动 &#xff5c; 第十七期』 本期书籍&#xff1a;《Python网络爬虫&#xff1a;从入门到实战》 赠书规则&#xff1a;评论区&#xff1a;点赞&#xff5c;收…

ORA-04031

ORA-04031&#xff1a;unable to allocate 3264 bytes of shared memory 1、问题背景 修改SGA重启数据库后报错系统内存&#xff1a;8G 原SGA大小&#xff1a;3G 修改后SGA大小&#xff1a;5G数据库可以正常重启&#xff0c;但是trance日志一直在报错 2、解决办法 调整shared…

React入门学习笔记3

事件处理 通过onXxx属性指定事件处理函数(注意大小写) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——为了更好的兼容性 eg&#xff1a;οnclick》onClickReact中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——为了更高效通过event.target得到发生…

ssh-keygen 做好免密登录后不生效

免密说明 通常情况下&#xff0c;我们ssh到其他服务器需要知道服务器的用户名和密码。对于需要经常登录的服务器每次都输入密码比较麻烦&#xff0c;因此我们可以在两台服务器上做免密登录&#xff0c;即在A服务器可以免密登录B服务器。 在A服务器上登录B服务器时&#xff0c;…