Glide's timeout control related processing

Author: newki

foreword

I believe that everyone is familiar with Glide. You should be familiar with various source code analysis and usage introduction. But have you encountered the timeout problem of setting Glide?

I met it and fell into the pit, the situation is like this.

  1. Call the interface to pull user avatars from the network. Currently, the amount of data is not large, roughly more than 1,000 people. (Using a custom queue)
  2. Use Glide to download the avatar to the local sandbox File (for the convenience of caching, it will be faster next time).
  3. Recognize the face information in the avatar, and generate a face Bitmap, (it has a success and failure processing and retry mechanism)
  4. Generate the features corresponding to the face, and save the face feature data and face feature picture to the sandbox File.
  5. Encapsulate the face object and load it into memory to maintain a global singleton.
  6. Scenario business: face comparison with the live face obtained in the preview screen of the camera.

At the beginning, the timeout period was not set. As a result, the custom queue for Glide to download pictures often freezes, causing the entire queue to execute slowly or even fail to execute. The entire registration service is blocked, and new users have been waiting for too long or even Unable to register.

The problem is the problem of picture loading. Some pictures cannot be loaded, some pictures are too large and take too long to load, some are not pictures at all, some network is slow, unstable, or there is no network at all, and some are access rights issues. In order to let The picture download queue can work normally. Glide's timeout mechanism has been added, and the road to stepping on the pit begins.

1. Problem recurrence

Everyone should clear up the use of Glide, how to add timeout, here is a sample code:

rely:

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'

The download method is encapsulated with an extension method:

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)
            }
        })

}

Everyone uses tools or directly writes in Glide to have the same effect, without affecting the final result.

use:


    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}")

    })

Taking the picture URL of Amazon Cloud Service as an example, what are the differences under different network conditions and different network loading framework conditions.

1.1 When HttpURLConnection is offline

The network request source code of native Glide is in the HttpUrlFetcher class.

specific method:

Even if we set the timeout period in buildAndConfigureConnection, the connect method will directly report an error, and it will not follow the logic of timeout

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

1.1 HttpURLConnection is connected to the network but not connected

What if there is a network, but the network does not work?

It will indeed wait for a while now, since the timeout period we set is 15 seconds, print the Log to see.

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

The error is the same as above, but with a timeout of 10 seconds:

Hey, are you playing with me? Then I changed the timeout period of Glide to 5000, which is 5 seconds, but the final result is still 10 seconds.

Why is this? Although it is connected to WIFI, but there is no network, it still cannot resolve the hostname, and the timeout of this stage defined inside HttpURLConnection is 10 seconds.

We can copy the source code of Glide's network request and try it out!

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();
        }
    }
}

In order to distinguish it from the previous one, we set a timeout of 7 seconds and see what changes the result?

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

The mistake is already obvious, hey

1.1 HttpURLConnection has Netcom, but no access rights

Then I connect to the Internet and turn off the authorization. Although I can resolve the domain name, I don’t have access rights, and I still can’t get pictures. What will happen at this time?

We still set a timeout of 15 seconds:

 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)
            }

        })

Error message, this time the network request is indeed through, and it is indeed in the timeout.

But why is this time 30 seconds?

What if we set the timeout to 20 seconds? Then the result is 40 seconds!

Is the problem with HttpURLConnection? We still use the native HttpURLConnection code access with a 7-second timeout in the previous step to try!

You can see that the result is in line with our expected 7-second timeout.

So why does Glide's default HttpURLConnection have twice the timeout?

It is because Glide internally retries the request of HttpURLConnection.

When it times out for the first time, it will go to the error callback, but it does not call back, but handles it by itself.

It's really too confusing, I don't know how to learn to retry, I want you to mind your own business...

1.1 Change to OkHttp3

If you get rid of this set of HttpURLConnection logic and retry logic, Glide also provides an interface for third-party network requests, such as our commonly used OkHttp to load pictures.

You should be familiar with it, just add the dependency library:

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

At this time, it has been replaced with OkHttp to load, and its default timeout time is 10 seconds. At this time, it is invalid for us to modify the timeout time of 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)
            }

        })

Not to mention changing it to 20 seconds, even changing it to 100 seconds will not work! Because these configurations are to modify the default timeout time of HttpURLConnection. The loading of OkHttp doesn't go that way at all.

Print the Log as follows:

Hey, it’s really a big head. Didn’t it say it’s ready to use out of the box? Why are there so many problems and so many situations, I really don’t know what to do.

2. Problem solving 1, using OkHttp3's custom Client

Since we cannot modify the timeout time in Glide after using OkHttp, can we directly modify the timeout time of OkHttp?

Everyone has configured it more or less, so paste the code directly here:

@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()));
    }
}

Implement our own OkHttpClient class:

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;
            }
        };
    }

}

You can see that we have set a timeout of 15 seconds, and the printed results are as follows:

If you want to set a few seconds, it is a few seconds. There is no retry and the time is wrong. This is indeed a solution.

3. Problem solving 2, use coroutine timeout

Another solution is to use the timeout of the coroutine to control. Since Glide's image loading and callback processing are implemented by anonymous functions, we first use coroutine processing to flatten the callback for internal callback processing.

As I said before, here is the code directly

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("加载失败了"))

                }
            })

    }
}

When we use it, we are the timeout function of the coroutine. No matter what the bottom layer is implemented, we can directly intercept the timeout of the upper layer.

    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)
        }
    }

This is also a more convenient solution.

postscript

If it is a network request, whether it is the Http of the interface or the image loading of Glide, we can use OkHttp to load, and we can set the Timeout property of OkHttpClient to set the timeout.

For other asynchronous operations, we can also use the timeout function of the coroutine to directly cancel the coroutine at the upper layer, which can also achieve the goal.

Both methods are available. Personally, I chose the coroutine timeout method, because I found that in some cases, even if the OkHttp timeout is set, it still occasionally times out for a long time. If the network connection is slow or unstable, if the server does not respond in time or the response time is too long, the timeout mechanism will not work. So to be on the safe side, use the coroutine timeout to directly process it on the upper layer. After the update, it is currently running in good condition.

Android study notes

Android Performance Optimization: https://qr18.cn/FVlo89
Android Vehicle: https://qr18.cn/F05ZCM
Android Reverse Security Study Notes: https://qr18.cn/CQ5TcL
Android Framework Principles: https://qr18.cn/AQpN4J
Android Audio and Video: https://qr18.cn/Ei3VPD
Jetpack (including Compose): https://qr18.cn/A0gajp
Kotlin: https://qr18.cn/CdjtAF
Gradle: https://qr18.cn/DzrmMB
OkHttp Source Code Analysis Notes: https://qr18.cn/Cw0pBD
Flutter: https://qr18.cn/DIvKma
Android Eight Knowledge Body: https://qr18.cn/CyxarU
Android Core Notes: https://qr21.cn/CaZQLo
Android Past Interview Questions: https://qr18.cn/CKV8OZ
2023 Latest Android Interview Question Collection: https://qr18.cn/CgxrRy
Android Vehicle Development Job Interview Exercises: https://qr18.cn/FTlyCJ
Audio and Video Interview Questions:https://qr18.cn/AcV6Ap

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/132230144