Glides Verarbeitung im Zusammenhang mit der Timeout-Steuerung

Autor: newki

Vorwort

Ich glaube, dass jeder mit Glide vertraut ist und mit verschiedenen Quellcodeanalysen und Einführungen in die Verwendung vertraut sein sollte. Aber sind Sie auch auf das Timeout-Problem beim Einstellen von Glide gestoßen?

Ich traf es und fiel in die Grube, die Situation ist so.

  1. Rufen Sie die Schnittstelle auf, um Benutzeravatare aus dem Netzwerk abzurufen. Derzeit ist die Datenmenge nicht groß, etwa mehr als 1.000 Personen. (Verwenden einer benutzerdefinierten Warteschlange)
  2. Verwenden Sie Glide, um den Avatar in die lokale Sandbox-Datei herunterzuladen (zur Vereinfachung des Cachings wird es beim nächsten Mal schneller sein).
  3. Erkennen Sie die Gesichtsinformationen im Avatar und generieren Sie eine Gesichts-Bitmap (es verfügt über einen Erfolgs- und Fehlerverarbeitungs- und Wiederholungsmechanismus).
  4. Generieren Sie die dem Gesicht entsprechenden Merkmale und speichern Sie die Gesichtsmerkmalsdaten und das Gesichtsmerkmalsbild in der Sandbox-Datei.
  5. Kapseln Sie das Gesichtsobjekt und laden Sie es in den Speicher, um einen globalen Singleton zu verwalten.
  6. Szenariogeschäft: Gesichtsvergleich mit dem Live-Gesicht, das im Vorschaubildschirm der Kamera angezeigt wird.

Zu Beginn wurde kein Timeout-Zeitraum festgelegt. Dies führt dazu, dass die benutzerdefinierte Warteschlange für Glide zum Herunterladen von Bildern häufig einfriert, was dazu führt, dass die gesamte Warteschlange langsam ausgeführt wird oder sogar nicht ausgeführt werden kann. Der gesamte Registrierungsdienst ist blockiert, und neue Benutzer haben dies getan Ich habe zu lange gewartet oder konnte mich gar nicht registrieren.

Das Problem ist das Problem beim Laden von Bildern: Einige Bilder können nicht geladen werden, einige Bilder sind zu groß und das Laden dauert zu lange, einige sind überhaupt keine Bilder, einige Netzwerke sind langsam, instabil oder es gibt überhaupt kein Netzwerk usw Bei einigen handelt es sich um Probleme mit Zugriffsrechten. Damit die Bild-Download-Warteschlange normal funktionieren kann, wurde der Timeout-Mechanismus von Glide hinzugefügt, und der Weg zum Betreten der Grube beginnt.

1. Wiederauftreten des Problems

Jeder sollte sich über die Verwendung von Glide und das Hinzufügen einer Zeitüberschreitung im Klaren sein. Hier ist ein Beispielcode:

verlassen:

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'

Die Download-Methode ist mit einer Erweiterungsmethode gekapselt:

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

}

Jeder nutzt Tools oder schreibt direkt in Glide, um den gleichen Effekt zu erzielen, ohne das Endergebnis zu beeinträchtigen.

verwenden:


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

    })

Welche Unterschiede bestehen unter verschiedenen Netzwerkbedingungen und unterschiedlichen Netzwerkauslastungsrahmenbedingungen am Beispiel der Bild-URL des Amazon Cloud Service?

1.1 Wenn HttpURLConnection offline ist

Der Netzwerkanforderungsquellcode von nativem Glide befindet sich in der HttpUrlFetcher-Klasse.

spezifische Methode:

Selbst wenn wir den Timeout-Zeitraum in buildAndConfigureConnection festlegen, meldet die Verbindungsmethode direkt einen Fehler und folgt nicht der Timeout-Logik

com.bumptech.glide.load.HttpException: Verbindung herstellen oder Daten abrufen fehlgeschlagen, Statuscode: -1

1.1 HttpURLConnection ist mit dem Netzwerk verbunden, aber nicht verbunden

Was ist, wenn ein Netzwerk vorhanden ist, das Netzwerk jedoch nicht funktioniert?

Es wird jetzt tatsächlich eine Weile warten, da die von uns festgelegte Zeitüberschreitungsdauer 15 Sekunden beträgt. Drucken Sie das Protokoll aus, um es anzuzeigen.

Klasse com.bumptech.glide.load.HttpException: Verbindung oder Datenabruf fehlgeschlagen, Statuscode: -1

Der Fehler ist derselbe wie oben, jedoch mit einem Timeout von 10 Sekunden:

Hey, spielst du mit mir? Dann habe ich den Timeout-Zeitraum von Glide auf 5000 geändert, also 5 Sekunden, aber das Endergebnis beträgt immer noch 10 Sekunden.

Warum ist das? Obwohl es mit WIFI verbunden ist, aber kein Netzwerk vorhanden ist, kann der Hostname immer noch nicht aufgelöst werden, und das in HttpURLConnection definierte Zeitlimit für diese Phase beträgt 10 Sekunden.

Wir können den Quellcode der Netzwerkanfrage von Glide kopieren und ausprobieren!

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

Um es vom vorherigen zu unterscheiden, stellen wir ein Timeout von 7 Sekunden ein und schauen, was sich am Ergebnis ändert?

java.net.UnknownHostException: Host „s3.ap-southeast-1.amazonaws.com“ kann nicht aufgelöst werden: Dem Hostnamen ist keine Adresse zugeordnet

Der Fehler ist schon offensichtlich, hey

1.1 HttpURLConnection hat Netcom, aber keine Zugriffsrechte

Dann verbinde ich mich mit dem Internet und schalte die Autorisierung aus. Obwohl ich den Domänennamen auflösen kann, habe ich keine Zugriffsrechte und kann immer noch keine Bilder abrufen. Was wird zu diesem Zeitpunkt passieren?

Wir stellen weiterhin ein Timeout von 15 Sekunden ein:

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

        })

Fehlermeldung, dieses Mal ist die Netzwerkanforderung tatsächlich durchgegangen und befindet sich tatsächlich im Timeout.

Aber warum beträgt diese Zeit 30 Sekunden?

Was wäre, wenn wir das Timeout auf 20 Sekunden festlegen würden? Dann beträgt das Ergebnis 40 Sekunden!

Liegt das Problem bei HttpURLConnection? Zum Ausprobieren verwenden wir im vorherigen Schritt immer noch den nativen HttpURLConnection-Codezugriff mit einem Timeout von 7 Sekunden!

Sie können sehen, dass das Ergebnis unserem erwarteten Timeout von 7 Sekunden entspricht.

Warum hat Glides Standard-HttpURLConnection also das doppelte Timeout?

Dies liegt daran, dass Glide die Anforderung von HttpURLConnection intern erneut versucht.

Wenn zum ersten Mal eine Zeitüberschreitung auftritt, wird der Fehlerrückruf aufgerufen, aber er ruft nicht zurück, sondern behandelt ihn selbst.

Es ist wirklich zu verwirrend, ich weiß nicht, wie ich lernen soll, es noch einmal zu versuchen, ich möchte, dass Sie sich um Ihre eigenen Angelegenheiten kümmern ...

1.1 Wechsel zu OkHttp3

Wenn Sie diesen Satz von HttpURLConnection-Logik und Wiederholungslogik entfernen, stellt Glide auch eine Schnittstelle für Netzwerkanforderungen von Drittanbietern bereit, z. B. unser häufig verwendetes OkHttp zum Laden von Bildern.

Sie sollten damit vertraut sein. Fügen Sie einfach die Abhängigkeitsbibliothek hinzu:

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

Zu diesem Zeitpunkt wurde es zum Laden durch OkHttp ersetzt und die Standard-Timeout-Zeit beträgt 10 Sekunden. Zu diesem Zeitpunkt ist es für uns ungültig, die Timeout-Zeit von Glide zu ändern.

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

        })

Ganz zu schweigen von der Änderung auf 20 Sekunden, selbst eine Änderung auf 100 Sekunden wird nicht funktionieren! Da diese Konfigurationen dazu dienen, die Standard-Timeout-Zeit von HttpURLConnection zu ändern. Das Laden von OkHttp funktioniert überhaupt nicht.

Drucken Sie das Protokoll wie folgt aus:

Hey, es ist wirklich ein großer Kopf. Steht da nicht, dass es sofort einsatzbereit ist? Warum gibt es so viele Probleme und so viele Situationen, ich weiß wirklich nicht, was ich tun soll.

2. Problemlösung 1 mit dem benutzerdefinierten Client von OkHttp3

Können wir die Timeout-Zeit von OkHttp direkt ändern, da wir die Timeout-Zeit in Glide nach der Verwendung von OkHttp nicht ändern können?

Jeder hat es mehr oder weniger konfiguriert, also fügen Sie den Code direkt hier ein:

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

Implementieren Sie unsere eigene OkHttpClient-Klasse:

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

}

Sie können sehen, dass wir eine Zeitüberschreitung von 15 Sekunden festgelegt haben und die gedruckten Ergebnisse wie folgt lauten:

Wenn Sie ein paar Sekunden einstellen möchten, sind es ein paar Sekunden. Es gibt keinen erneuten Versuch und die Zeit ist falsch. Das ist tatsächlich eine Lösung.

3. Problemlösung 2, Coroutine-Timeout verwenden

Eine andere Lösung besteht darin, das Timeout der Coroutine zur Steuerung zu verwenden. Da das Laden von Bildern und die Rückrufverarbeitung von Glide durch anonyme Funktionen implementiert werden, verwenden wir zunächst die Coroutine-Verarbeitung, um den Rückruf für die interne Rückrufverarbeitung zu reduzieren.

Wie ich bereits sagte, hier ist der Code direkt

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

                }
            })

    }
}

Wenn wir es verwenden, stellen wir die Timeout-Funktion der Coroutine dar. Unabhängig davon, was die untere Ebene implementiert, können wir das Timeout der oberen Ebene direkt abfangen.

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

Dies ist auch eine bequemere Lösung.

Nachwort

Wenn es sich um eine Netzwerkanforderung handelt, sei es das HTTP der Schnittstelle oder das Laden von Glide-Bildern, können wir OkHttp zum Laden verwenden und die Timeout-Eigenschaft von OkHttpClient festlegen, um das Timeout festzulegen.

Für andere asynchrone Vorgänge können wir auch die Timeout-Funktion der Coroutine verwenden, um die Coroutine direkt auf der oberen Ebene abzubrechen, was ebenfalls das Ziel erreichen kann.

Beide Methoden sind verfügbar. Ich persönlich habe mich für die Coroutine-Timeout-Methode entschieden, da ich festgestellt habe, dass es in einigen Fällen, selbst wenn das OkHttp-Timeout festgelegt ist, gelegentlich immer noch zu einer langen Zeitüberschreitung kommt. Wenn die Netzwerkverbindung langsam oder instabil ist, der Server nicht rechtzeitig antwortet oder die Antwortzeit zu lang ist, funktioniert der Timeout-Mechanismus nicht. Um auf der sicheren Seite zu sein, nutzen Sie das Coroutine-Timeout, um es direkt auf der oberen Ebene zu verarbeiten. Nach dem Update läuft es derzeit in gutem Zustand.

Android-Studiennotizen

Android-Leistungsoptimierung: https://qr18.cn/FVlo89
Android-Fahrzeug: https://qr18.cn/F05ZCM
Android-Reverse-Security-Studiennotizen: https://qr18.cn/CQ5TcL
Android-Framework-Prinzipien: https://qr18.cn/AQpN4J
Android-Audio und -Video: https://qr18.cn/Ei3VPD
Jetpack (einschließlich Compose): https://qr18.cn/A0gajp
Kotlin: https://qr18.cn/CdjtAF
Gradle: https://qr18.cn/DzrmMB
OkHttp Quellcode-Analysenotizen: https://qr18.cn/Cw0pBD
Flutter: https://qr18.cn/DIvKma
Android Eight Knowledge Body: https://qr18.cn/CyxarU
Android-Kernnotizen: https://qr21.cn/CaZQLo
Android Frühere Interviewfragen: https://qr18.cn/CKV8OZ
2023 Neueste Android-Interviewfragensammlung: https://qr18.cn/CgxrRy
Bewerbungsgesprächsübungen für die Entwicklung von Android-Fahrzeugen: https://qr18.cn/FTlyCJ
Audio- und Videointerviewfragen:https://qr18.cn/AcV6Ap

Je suppose que tu aimes

Origine blog.csdn.net/weixin_61845324/article/details/132230144
conseillé
Classement