Glide 的超时操控相关处理
前语
Glide 相信咱们都不生疏,各种源码剖析,运用介绍咱们应该都是烂熟于心。可是设置 Glide 的超时问题咱们遇到过没有。
我遇到了,并且掉坑里了,状况是这样的。
- 调用接口从网络拉取用户头像,现在数据量不大,大致1000多个人。(用了自定义行列)
- 运用 Glide 下载头像到本地沙盒 File (为了便利的缓存下次更快)。
- 识别头像中的人脸信息,并生成人脸Bitmap,(本身有成功失利的处理与重试机制)
- 生成人脸对应的特征,并保存人脸特征数据和人脸特征图片到沙盒 File 。
- 封装人脸对象并加载到内存中坚持大局单例。
- 场景业务:与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 直接上层处理了,更新之后现在运行状况良好。
代码比较简答都已经在文中贴出。
假如本文的解说有什么错漏的当地,期望同学们一定要指出哦。有疑问也能够评论区交流学习进步,谢谢!
当然假如觉得本文还不错对你有些协助的话,还请点赞
支撑一下哦,你的支撑是我最大的动力啦!
Ok,这一期就此结束。