Glide 的超时操控相关处理

前语

Glide 相信咱们都不生疏,各种源码剖析,运用介绍咱们应该都是烂熟于心。可是设置 Glide 的超时问题咱们遇到过没有。

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

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

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

问题嘛,便是图片加载的问题,有些图片无法加载,有些图片太大加载时刻过长,有些根本就不是图片,有些网络慢,不稳定,或许干脆就无网,有些是拜访权限问题,为了让图片下载行列能正常工作参加了 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 类中。

具体办法:

坏了,Glide的超时居然失控了!这可如何是好

就算咱们在 buildAndConfigureConnection 中设置了超时时刻,可是 connect 办法直接就报错了,也不会走timeout的逻辑

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

坏了,Glide的超时居然失控了!这可如何是好

1.1 HttpURLConnection 有网的可是不通

那假如有网,可是网不通呢?

这下的确会等候一小会了,由于咱们设置的超时时刻是15秒,打印Log看看。

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

过错和上面相同,可是超时时刻是10秒:

坏了,Glide的超时居然失控了!这可如何是好

喂,玩我是吧。那我改 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

坏了,Glide的超时居然失控了!这可如何是好

过错已经很明显了,哎

坏了,Glide的超时居然失控了!这可如何是好

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 里面了。

坏了,Glide的超时居然失控了!这可如何是好

可是这个时刻为什么是30秒?

坏了,Glide的超时居然失控了!这可如何是好

假如咱们设置超时时刻是20秒?那么成果便是40秒!

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

坏了,Glide的超时居然失控了!这可如何是好

能够看到成果是符合咱们预期的7秒超时。

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

是由于 Glide 内部对 HttpURLConnection 的恳求做了重试处理。

坏了,Glide的超时居然失控了!这可如何是好

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

坏了,Glide的超时居然失控了!这可如何是好

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

坏了,Glide的超时居然失控了!这可如何是好

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 如下:

坏了,Glide的超时居然失控了!这可如何是好

坏了,Glide的超时居然失控了!这可如何是好

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

坏了,Glide的超时居然失控了!这可如何是好

二、问题解决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秒的超时,打印的成果如下:

坏了,Glide的超时居然失控了!这可如何是好

想设置几秒便是几秒,没有重试导致时刻不对一说。这的确是一种计划。

三、问题解决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,这一期就此结束。

坏了,Glide的超时居然失控了!这可如何是好