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://xiahunao.cn/news/1381024.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

高通量DNA测序数据的生物信息学方法

来源&#xff1a;大数据期刊 时间&#xff1a;2016-05-13 14:41:09 作者&#xff1a;詹晓娟 姚登举 朱怀球 詹晓娟1&#xff0c;姚登举2&#xff0c;朱怀球3 1. 黑龙江工程学院计算机科学与技术学院&#xff0c;黑龙江 哈尔滨 150050&#xff1b; 2. 哈尔滨理工大学软件学院…

C++ pair详解

pair pair在cplusplus 与CPrimer中的介绍 首先可以看到pair是一个class template —类模板 pair也是一种模板类型。 对 pair 的介绍是&#xff1a; 这个类将一对值耦合在一起&#xff0c;这些值可能是不同类型的(T1和T2)。单个值可以通过其公共成员first和second访问。 pair是…

聊聊OpenStack运维架构

前言 想一想&#xff0c;从事OpenStack杂七杂八的事儿&#xff0c;至今正好三年半了。做过QA测试&#xff08;手动的、自动的&#xff09;、CI&#xff08;gerrit、jenkins、gitlab、harbor&#xff09;、云产品封装&#xff08;从系统pxe到openstack代码&#xff09;、自动化…

Lua pairs与ipairs效率分析

介于大家目前有些人比较关心 lua table中pairs 和 ipairs的效率问题, 特此研究了一下... 如有不正 还需指出.. 首先来看下 lua中table的结构定义: table中分为2个存储空间, 一个是线性数组空间(TValue *array), 和一个hash空间(Node *node) 当我们使用 pairs 和 ipairs 会产生…

【机器学习|数学基础】Mathematics for Machine Learning系列之矩阵理论(22):方阵函数在微分方程组中的应用

目录 前言往期文章5.6 方阵函数在微分方程组中的应用5.6.1 解一阶线性常系数齐次微分方程组5.6.2 解一阶线性常系数非齐次微分方程组 结语 前言 Hello&#xff01;小伙伴&#xff01; 非常感谢您阅读海轰的文章&#xff0c;倘若文中有错误的地方&#xff0c;欢迎您指出&#xf…

C++中pair使用详细说明

一、pair 的介绍 pair 是一个很实用的 "小玩意"&#xff0c;当想要将两个元素绑在一起作为一个合成元素、又不想要因此定义结构体时&#xff0c;使用 pair 可以很方便地作为一个代替品。 也就是说&#xff0c;pair 实际上可以看作一个内部有两个元素的结构体&#xf…

Solidity实现默克尔树 Merkle Tree

​​Merkle Tree​​​&#xff0c;也叫默克尔树或哈希树&#xff0c;是区块链的底层加密技术&#xff0c;被BTC和Ethereum区块链广泛采用。​​Merkle Tree​​​是一种自下而上构建的加密树&#xff0c;每个叶子是对应数据的哈希&#xff0c;而每个非叶子为它的​​2​​个子…

论文浅尝 | PairRE: 通过成对的关系向量实现知识图谱嵌入

笔记整理&#xff1a;黎洲波&#xff0c;浙江大学硕士&#xff0c;研究方向为自然语言处理、知识图谱。 研究背景 知识图谱因其在问答、语义解析和命名实体消歧等任务取得了良好的效果而受到广泛关注&#xff0c;而大部分知识图谱都存在不全和缺失实体链接的问题&#xff0c;所…

致敬乔布斯的经典,锤子坚果Pro成2017年最受欢迎手机看罗永浩怎么说

锤子坚果Pro发布已经近2个月&#xff0c;但热度依旧不减。在刚刚过去的京东618活动中&#xff0c;坚果Pro在1500到2000元档位产品中一举斩获单品销量冠军。坚果Pro凭借出色的销售战绩坐实2017手机圈“黑马”之名&#xff0c;而其销量节节攀升&#xff0c;这其中必有一番原因。日…

坚果Pro 2安抚了不少人锤粉, 但用户更期待锤子T3

今年秋季&#xff0c;锤子科技创始人罗永浩于2017年11月7日在成都发布坚果系列2代手机坚果Pro 2&#xff0c;指纹和logo的融合增强了手机的一体型。软件上的再度优化&#xff0c;帮助盲人更注重人文关怀&#xff0c;再从罗永浩自带“流量”&#xff0c;坚果Pro2自然而然受到大家…

pro坚果android耗流量,深度使用坚果Pro3一个月,憋了一肚子话,不吐不快​

原标题&#xff1a;深度使用坚果Pro3一个月&#xff0c;憋了一肚子话&#xff0c;不吐不快​ 罗永浩创办的锤子手机曾经在国内手机市场&#xff0c;也是一枚耀眼的新兴&#xff0c;罗永浩对于手机工业设计的高标准严要求让锤子手机成为了国内少有的在设计上能和苹果三星比肩的手…

厉害了!原来这些文艺明星都喜欢锤子坚果Pro

最近在手机圈出现了一匹黑马&#xff0c;那就是锤子坚果Pro。在京东618期间取得了十分骄人的战果&#xff0c;荣获6月1日至18日1500-1999元价位档单品销量第一&#xff0c;成为该价格区间最受欢迎的手机&#xff0c;同时在2017年4月1日后首发的新品销量排名中位列第三&#xff…

锤子t1android驱动,锤子T1痛失安卓5.1!都是因为这?

现如今很多高端手机都开始升级安卓6.0了&#xff0c;但是情怀锤子却突然给了老用户一“锤子”&#xff0c;宣布第一代T1将不会升级到安卓5.1&#xff0c;因为“优化效果不明显”。 这顿时引发了一片争议。有的T1用户表示了理解&#xff0c;称手机够用就好&#xff0c;不在乎系统…

内蒙古大学计算机考研资料汇总

内蒙古大学研招网 内蒙古大学计算机学院 内蒙古大学计算机学院成立于1997年&#xff0c;其前身是1978年设置的计算机专业和1988年成立的计算机科学系。内蒙古大学软件学院成立于2005年&#xff0c;与计算机学院为一个实体&#xff0c;两个牌子。目前学院由计算机科学系、…

如何获取bainu文档并用斡仑office进行编码转换-永中office蒙文版

声明&#xff1a; 1.bainu软件是由内蒙古卓嘎信息技术有限公司研发的。 2.斡仑office是由内蒙古斡仑科技有限公司与永中软件股份有限公司联合开发的蒙汉多文种跨平台办公套装。 第一&#xff0c;首先我们打开bainu软件&#xff0c;如图&#xff1a; 第二&#xff0c;下列图中…

为什么 Mixin 被认为是有害的

为什么 Mixin 被认为是有害的 Mixin 是在 Vue 2 中引入的&#xff0c;作为组件之间共享代码的解决方案&#xff0c;这种方式成为许多代码库不可或缺的一部分。然而&#xff0c;随着时间的推移&#xff0c;它们的使用开始出现问题。尽管 mixins 最初很有吸引力&#xff0c;但现…

【从零开始学习JAVA | 第四十五篇】反射

目录 前言&#xff1a; ​反射&#xff1a; 使用反射的步骤&#xff1a; 1.获取阶段&#xff1a; 2.使用阶段&#xff1a; 反射的应用场景&#xff1a; 使用反射的优缺点&#xff1a; 总结&#xff1a; 前言&#xff1a; Java中的反射是一项强大而灵活的功能&#xff0…

1688采源宝的商家靠谱吗 怎么入驻成为阿里采源宝商家

说到采源宝&#xff0c;我想很多微商都是不陌生的&#xff0c;采源宝的主要作用就是方便微商查看并转发供应商所发布的商品&#xff0c;并在有客户下单时&#xff0c;还可以轻松向供货商去下单。但也有很多朋友对采源宝的商家靠谱吗这个问题存在很大疑惑&#xff0c;下面我们就…

开发nft数字藏品平台合法合规吗?

开发nft数字产品平台合法合规吗&#xff1f;这是很多人都在问的问题&#xff0c;那么今天就来给大家说说。 开发nft数字产品平台可以肯定的是合法合规。 其实很多人对国内国外的玩法不太了解&#xff0c;有很多人对数字产品法律和政策不太清楚。首先&#xff0c;我们要了解国内…

最近发现一款拍卖转拍,玩家互动挑选自己想要的拍品,转拍获利,还可以提货的源码,一款购物和赚钱的不二选择

拍卖转拍,玩家互动挑选自己想要的拍品,转拍获利,还可以提货,一款购物和赚钱的不二选择操作步骤打开拍卖首页 根据时间点击进入指定的场次选作品&#xff0c; 等待到时间准备抢拍, 抢完之后可以查看卖家的收款码, 确认打款之后可以上传支付截图 然后可以等待卖家确认收货, …