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 = 0L
    var endMillis = 0L


    GlideApp.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() {}

        @Override
        public 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 {

    @Override
    public 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() {

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[]{};
            }
        }};
    }

    private static HostnameVerifier getHostnameVerifier() {
        return new HostnameVerifier() {
            @Override
            public 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot复习:(31)Controller中返回的对象是如何转换成json字符串给调用者的?

首先&#xff0c;SpringBoot自动装配了HttpMessageConvertersAutoConfiguration这个自动配置类 而这个自动配置类又通过Import注解导入了JacksonHttpMessageConvertersConfiguration类&#xff0c; 在这个类中配置了一个类型为MappingJackson2HttpMessageConverter类型的bean…

2023年,App运行小游戏,可以玩出什么创意?

疫情过后&#xff0c;一地鸡毛。游戏行业的日子也不好过。来看看移动游戏收入&#xff1a;2022年&#xff0c;移动游戏收入达到920亿美元&#xff0c;同比下降6.4%。这告诉我们&#xff0c;2022年对移动游戏市场来说是一个小挫折。 但不管是下挫还是上升&#xff0c;移动游戏市…

热成像技术创新,助力人工智能炼就黑夜中的火眼金睛

原创 | 文 BFT机器人 普渡大学&#xff08;Purdue University&#xff09;的研究人员利用他们正在申请专利的方法来改进传统的机器视觉和感知&#xff0c;从而推动机器人技术和自动控制领域的发展。 埃尔莫尔家族电气与计算机工程学院&#xff08;Elmore Family School of Ele…

【链表OJ 5】合并两个有序链表

前言: &#x1f388;欢迎大家来到Dream_Chaser&#xff5e;的博客&#x1f388; 本文收录于 C--数据结构刷题的专栏中 -->http://t.csdn.cn/n6UEP 首先欢迎大家的来访&#xff0c;其次如有错误&#xff0c;非常欢迎大家的指正&#xff01;我会及时更正错误&#xff01; 目录…

【已解决】Java 中使用 ES 高级客户端库 RestHighLevelClient 清理百万级规模历史数据

&#x1f389;工作中遇到这样一个需求场景&#xff1a;由于ES数据库中历史数据过多&#xff0c;占用太多的磁盘空间&#xff0c;需要定期地进行清理&#xff0c;在一定程度上可以释放磁盘空间&#xff0c;减轻磁盘空间压力。 &#x1f388;在经过调研之后发现&#xff0c;某服务…

git的简单介绍和使用

git学习 1. 概念git和svn的区别和优势1.1 区别1.2 git优势 2. git的三个状态和三个阶段2.1 三个状态&#xff1a;2.2 三个阶段&#xff1a; 3. 常用的git命令3.1 下面是最常用的命令3.2 git命令操作流程图如下&#xff1a; 4. 分支内容学习4.1 项目远程仓库4.2 项目本地仓库4.3…

IPv4编址及子网划分

IPv4编址及子网划分 一、IPv4地址概述1.1、IPv4报文结构1.2、IPv4地址分类1.2.1、A类1.2.2、B类1.2.3、C类1.2.4、D类1.2.5、E类 1.3、私有IP地址1.4、特殊地址 二、子网划分2.1、子网掩码2.2、VLSM 可变长的子网掩码2.3、子网划分2.4、子网划分示例2.4.1、子网划分案例 —— A…

【24择校指南】温州大学计算机考研考情分析

温州大学(C) 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分数人数统计&#xff09;、院校概况、23专业目录、23复试详情、各科目以及各专业考情分析。 正文1349字&#xff0c;预计阅读&#xff1a;3分钟。 2023考情概况 温州…

IDEA新建类时自动设置类注释信息,署名和日期

IDEA设置路径 File --> Settings --> Editor --> File and Code Templates --> Include --> File Header 官方模板 这里 ${USER} 会读取计算机的用户名 ${DATE}是日期 ${TIME}是时间 /*** Author ${USER}* Date ${DATE} ${TIME}* Version 1.0*/

探索自动化网页交互的魔力:学习 Selenium 之旅【超详细】

"在当今数字化的世界中&#xff0c;网页自动化已经成为了不可或缺的技能。想象一下&#xff0c;您可以通过编写代码&#xff0c;让浏览器自动执行各种操作&#xff0c;从点击按钮到填写表单&#xff0c;从网页抓取数据到进行自动化测试。学习 Selenium&#xff0c;这一功能…

Small Tip: 如何Debug Start Routine

我也不知道咋地&#xff0c;在generated ABAP里面打断点进不去。 我也不晓得怎么弄&#xff0c;今天反正是硬找着去弄。不晓得有没有其他好办法。有知道的小伙伴评论下吧。 1、 在DTP里面选Before Transformation&#xff0c;要去debug start routine选这个就够了。其他的随意…

【Java可执行命令】(十七)JVM运行时信息动态维护工具 jinfo:一个维护 JVM 相关的配置参数和系统属性的工具,辅助故障排除、诊断和优化 ~

Java可执行命令之jinfo 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 -flags&#xff1a;查看进程的启动参数3.3 -sysprops&#xff1a;查看进程的系统属性3.4 -flag < name>&#xff1a;查看特定虚拟机参数的值3.5 -flag [/-]< name>&#xff1a;启用或禁…

【在树莓派上安装cpolar内网穿透实战】

文章目录 前言1.在树莓派上安装cpolar2.查询cpolar版本号3.激活本地cpolar客户端4.cpolar记入配置文件 前言 树莓派作为一个超小型的电脑系统&#xff0c;虽然因其自身性能所限&#xff0c;无法如台式机或笔记本等准系统一样&#xff0c;运行大型软件或程序&#xff08;指望用…

VsCode美化 - VsCode自定义 - VsCode自定义背景图

VsCode美化 - VsCode自定义 - VsCode自定义背景图&#xff1a;添加二次元老婆图到VsCode 前言 作为一个二刺螈&#xff0c;VsCode用久了&#xff0c;总觉得少了些什么。是啊&#xff0c;高效的代码生产工具中怎么能没有老婆呢&#xff1f; 那就安装一个VsCode插件把老婆添加…

数学建模学习(9):模拟退火算法

模拟退火算法(Simulated Annealing, SA)的思想借 鉴于固体的退火原理&#xff0c;当固体的温度很高的时候&#xff0c;内能比 较大&#xff0c;固体的内部粒子处于快速无序运动&#xff0c;当温度慢慢降 低的过程中&#xff0c;固体的内能减小&#xff0c;粒子的慢慢趋于有序&a…

go重制版的海盗王gateserver网关服务端

海盗王原有的gateserver网关经常出现无故报错和掉地图的问题&#xff0c;经过反复修改都无法解决相关问题。 加上&#xff0c;原有的程序已经趋于古董级别&#xff0c;存在很大的兼容性问题。 以上&#xff0c;萌发了用go语言进行重新开发一个gateserver网关程序的想法&#xf…

sxs卡丢失数据如何找回?sxs卡数据丢失原因和修复办法分享!

说起sxs卡&#xff0c;你们是否有所了解呢&#xff1f;sxs卡具有很好的传输性能&#xff0c;能够存储照片和视频数据&#xff0c;主要被放置在索尼XDCAM EX型摄像机上。 而在使用sxs卡设备过程中&#xff0c;难免和其他设备一样&#xff0c;容易出现数据丢失情况。而如果丢失的…

ESP-IDF插件去除红色波浪线

如图&#xff0c;新装的ESP-IDF打开别人的工程有好多红色波浪线。 把这里的第一个文件夹删除&#xff0c;就是那个.vscode&#xff0c;接下来按ctrlshiftP&#xff0c;输入vscode&#xff0c; 选第一个&#xff0c;添加配置文件夹。 问题解决。 之后记得重新配置板子信息和串…

豪越HYDO智能运维助力智慧医院信息化建设

随着国家政策的推动与支持&#xff0c;医疗行业信息化应用不断普及&#xff0c;大数据、AI、医疗物联网等技术的应用&#xff0c;快速推动了电子病历、智慧服务、智慧管理的智慧医院建设和医院信息标准化建设&#xff0c;通过不断探索创新“智慧医院”服务模式&#xff0c;实现…

Java实现籍贯级联选择器

在工作中要求写一个籍贯的级联选择器&#xff0c;记录一下自己写这个级联选择器的过程&#xff0c;因为自己才刚开始工作&#xff0c;有很多地方都没有考虑的很清楚&#xff0c;希望各位大佬能给出建议。 一、需求 A:正常的23个省&#xff0c;籍贯由“省区/县/市”组成&#xf…