Modo de cadena de responsabilidad y extensión de los principios de OKHttp explicados

1. Prefacio:

1.1 El contenido principal de este artículo

1. Introducción al Modelo de Cadena de Responsabilidad

2. El principio llamante de la cadena de responsabilidad en OKHttp

3. Cinco interceptores y dos interceptores de clase extendida

4. Cómo usar el interceptor de clase de extensión

5. El uso de interceptores de clase extendida en escenarios reales

1.2 Dirección del proyecto OKHttp:

https://github.com/square/okhttp

1.3 Introducción a la serie OKHttp:

El proyecto OKHttp en sí todavía es muy grande e involucra muchos puntos de conocimiento, por lo que es difícil resumir todo el artículo en un solo artículo, por lo que explicaré OKHttp en detalle en una serie de artículos.

La serie va a escribir los siguientes artículos.

El concepto básico de la explicación del principio OKHttp - Lost Summer Blog - CSDN Blog

Modo de cadena de responsabilidad y extensión de la explicación del principio OKHttp (se espera que se publique el 18 de marzo)

Interceptor de reintento explicado por el principio OKHttp (se espera que se publique el 22 de marzo)

Interceptor de enrutamiento explicado por el principio OKHttp

Interceptor de caché explicado por el principio OKHttp

Interceptor de grupo de conexiones explicado por el principio OKHttp

Interceptor de solicitudes explicado por el principio OKHttp

PD: Se espera que el progreso de 1-2 artículos se actualice cada semana.

2. Introducción al Modelo de Cadena de Responsabilidad

2.1 ¿Qué es el Modelo de Cadena de Responsabilidad?

Definición del patrón de cadena de responsabilidad: para evitar el acoplamiento del remitente de la solicitud con múltiples controladores de solicitudes, todos los controladores de solicitudes están conectados en una cadena a través del objeto anterior recordando la referencia de su siguiente objeto; cuando ocurre una solicitud, la solicitud puede ser pasa por la cadena hasta que un objeto lo maneja.

2.2 El uso de Cadena de Responsabilidad en Android

Aunque el uso del modelo Chain of Responsibility en Android no es particularmente grande, también es bastante extenso.Además de OKHttp, la distribución de eventos y la representación capa por capa de ViewGroup también es un modelo típico de Chain of Responsibility.

3. Introducción a los interceptores

3.1 Orden de ejecución del interceptor

En OKHttp, hay un total de 5 interceptores incorporados, así como 2 conjuntos de interceptores extensibles (puede haber múltiples interceptores en el conjunto).

La secuencia de llamadas de toda la cadena de responsabilidad es la siguiente:

Colección de interceptores personalizados->RetryAndFollowUpInterceptor->BridgeInterceptor->CacheInterceptor->

ConnectInterceptor->Colección de interceptores de red->CallServerInterceptor.

El código final implementado es el siguiente:

    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

3.2 Introducción a los interceptores

Los cinco interceptores que vienen con OKHttp son los siguientes:

RetryAndFollowUpInterceptor: Principalmente responsable de reintentar y rastrear solicitudes. Y principalmente controla el número de reintentos para enviar la solicitud.

BridgeInterceptor: es el principal responsable de empalmar encabezados y analizar los datos para obtener la respuesta final después de recibir el contenido.

CacheInterceptor: como sugiere el nombre, el conector de caché se usa para cargar el caché.

ConnectInterceptor: el interceptor del grupo de conexiones, como sugiere el nombre, se utiliza para procesar el grupo de conexiones.

CallServerInterceptor: interceptor de solicitudes. Principal responsable de enviar y recibir la solicitud final.

Dos colecciones de interceptores personalizadas

interceptores: clasificado en primer lugar en el conjunto de interceptores, podemos usar este interceptor para procesar nuevamente los datos finales devueltos. Como el descifrado de datos, etc.

networkInterceptors: se clasifica entre el interceptor del grupo de conexiones y el interceptor de solicitudes en el conjunto de interceptores, y puede procesar la mayoría de los paquetes originales recibidos.

Cuarto, el principio de usar la cadena de responsabilidad en OKHttp

4.1 Principio de llamada de la cadena de responsabilidad

En primer lugar, todos los interceptores implementan la interfaz en Interceptor y solo hay un método intercept() en la interfaz. Cada interceptor notifica a la siguiente capa a través de este método y también acepta la respuesta de la siguiente capa a través del valor de retorno de este método.

public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    ...
}

Dos, comienza el proceso de solicitud de inicio. En el proceso de solicitud, primero se construirá una colección List y esta colección List se cargará con todos los interceptores utilizados. el código se muestra a continuación:

    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

En tercer lugar, cree una Cadena de objetos de lista enlazada, cuya clase de implementación sea RealInterceptorChain. RealInterceptorChain implementa la interfaz de Interceptor.Chain. Hay muchos métodos en la interfaz Chain, entre los cuales el método de proceder es responsable de la distribución descendente del proceso.

Hay muchos parámetros en el método de construcción RealInterceptorChain. Aquí solo observamos los interceptores y el índice. Los interceptores representan todos los interceptores, y el índice representa el número de ejecuciones. Se puede entender que Chain es responsable de la programación e interceptor es responsable de la ejecución.

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }

En cuarto lugar, llame al método de proceder para iniciar el proceso de la cadena de responsabilidad. Veamos el método de proceder en RealInterceptorChain:

En este método, índice+1, construye el objeto cadena del siguiente nivel.

Luego obtenga el interceptor actual, porque el índice = 0 en este momento, el interceptor correspondiente es RetryAndFollowUpInterceptor, y se llamará al método intercept().

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ...
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    ...

    return response;
  }

Quinto, en el método de intercepción. A través del objeto de cadena creado anteriormente, continúe distribuyendo hacia abajo utilizando su método de proceder.

Aquí tomamos el método de intercepción en RetryAndFollowUpInterceptor como ejemplo. Se puede ver que el objeto Chain construido en el paso anterior continúa distribuyéndose a la siguiente capa y, al mismo tiempo, obtiene el valor de retorno de la respuesta y finalmente devuelve la respuesta después de un cierto procesamiento. Lo mismo es cierto para otros procesos de intercepción.

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
        ...
        response = realChain.proceed(request, streamAllocation, null, null);
        ...
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }
      ...
    }
  }

Sexto, el interceptor se distribuye hacia abajo capa por capa.Cuando se ejecuta la última capa del interceptor CallServerInterceptor, dado que no necesita continuar distribuyéndose hacia abajo, puede devolver directamente la respuesta.

El método de intercept() en CallServerInterceptor es el siguiente:

public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    ...

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    ...
    return response;
  }

Cinco, el uso de interceptores personalizados.

También es muy simple agregar:

Primero generamos un interceptor y luego implementamos su interfaz de intercepción.

En esta interfaz, además de nuestra lógica personalizada, también necesitamos implementar la función de su cadena de responsabilidad:

return chain.proceed(chain.request())

1. Agregue un interceptor personalizado

 client.interceptors().add(object : Interceptor {
            override fun intercept(chain: Interceptor.Chain): Response {
                //...这里我们可以实现一些自定义逻辑
                return chain.proceed(chain.request())
            }
        })

2. Agregue un interceptor de red personalizado

 client.networkInterceptors().add(object : Interceptor {
            override fun intercept(chain: Interceptor.Chain): Response {
                //...这里我们可以实现一些自定义逻辑
                return chain.proceed(chain.request())
            }
        })

6. El uso de interceptores de clase extendida en escenarios reales

escena uno:

Aquí hay un ejemplo de un escenario real. Los requisitos son los siguientes:

Los datos devueltos por nuestro servidor están encriptados, pero para la lógica de la capa de aplicación, naturalmente queremos usar los datos desencriptados directamente. Esto requiere el uso de nuestro interceptor de aplicaciones. Después de recibir la respuesta, obtenemos los bytes devueltos para el descifrado y luego reconstruimos una respuesta y la devolvemos a la capa lógica.

builder.addInterceptor(object : Interceptor {
            override fun intercept(chain: Interceptor.Chain): Response {
                val response = chain.proceed(chain.request())
                val body = response.body()!!
                val bytes = body.bytes()
                val newBytes = decrypt(bytes)
                //解密转换
                val buffer = Buffer()
                buffer.write(newBytes)
                val newBody =
                    RealResponseBody(
                        body.contentType()!!.type() + File.separator + body.contentType()!!
                            .subtype(),
                        body.contentLength(),
                        buffer
                    )
                val responseBuilder = response.newBuilder()
                responseBuilder.body(newBody)
                val newRresponse = responseBuilder.build()
                return newRresponse
            }
        })

Escenario dos:

Aquí hay un ejemplo de un escenario real. Los requisitos son los siguientes:

Hay un área en nuestra página de inicio que muestra una lista de listados, y los datos de esta pieza provienen de los servicios. Esta lista, algunos usuarios están preocupados, algunos usuarios no están preocupados. Para continuar con la carga rápida de la página de inicio, esperamos usar el caché por primera vez. Para aquellos que se preocupan por esta lista, naturalmente desplegarán para actualizar. En este momento, esperamos enviar la solicitud directamente en lugar de usar el caché, y reemplace el original después de recibir la respuesta.Algunos cachés se actualizan debido al tiempo de valor de retorno más reciente.

Entonces, ¿cómo satisfacemos esta necesidad?

En primer lugar, tenemos que distinguir si usar el caché. Eso es simple Al construir la solicitud, podemos controlar si la solicitud usa caché a través de CacheControl.

        val builder = Request.Builder()
        val cacheBuilder = CacheControl.Builder()
        cacheBuilder.noStore()
        cacheBuilder.noStore()
        builder.cacheControl(cacheBuilder.build())
        val request = builder.url("https://www.baidu.com").build()

Por supuesto, también podemos usar otra forma más simple de implementar, porque la clave de si el caché existe o no es la url, luego, si no queremos usar el caché, agregue algunos bits de bandera para distinguir. Por ejemplo, una solicitud normal a https://www.server.com/api no utiliza la URL de solicitud en caché: https://www.server.com/api?cache=123.

Luego llegué al segundo punto de demanda. Esperaba que la respuesta se actualizara en el caché sin usar la solicitud almacenada en caché. ¿Qué debo hacer?

Ahora es nuestro turno de personalizar el interceptor. Suponiendo que usamos el segundo escenario anterior, podemos modificar la URL en la solicitud después de enviar la solicitud y recibir la respuesta, y volver a cambiar la URL a: https://www.server.com/api. En este caso, siga volviendo al interceptor de caché y su caché se actualizará. el código se muestra a continuación:

client.networkInterceptors().add(object : Interceptor {
            override fun intercept(chain: Interceptor.Chain): Response {
                var response = chain.proceed(chain.request())
                val request = chain.request()
                val url = request.url()
                val host = url.host()
                //判断如果host是带缓存的,则把host中的标记位消除掉
                if (host.endsWith("?cache=1")) {
                    val urlBuilder = url.newBuilder()
                    urlBuilder.host(host.replace("?cache=1", ""))

                    val requestBuilder = request.newBuilder()
                    requestBuilder.url(urlBuilder.build())

                    val responseBuilder = response.newBuilder()
                    responseBuilder.request(requestBuilder.build())
                    response = responseBuilder.build()
                }

                return response
            }
        })

Todo el proceso es el siguiente:

Usando la escena en caché:

URL de solicitud: https://www.server.com/api?cache=1, vaya al interceptor de caché. Si hay una respuesta a la solicitud en el interceptor de caché, la respuesta se devolverá directamente.

En lugar de usar una escena en caché:

URL de solicitud: https://www.server.com/api, vaya al interceptor de caché. Si no hay respuesta para la solicitud en el interceptor de caché, se envía la solicitud. Después de recibir la respuesta, cambie la dirección de solicitud a https://www.server.com/api?needcache=true. Al volver al interceptor de caché, https://www.server.com/api?needcache=true se actualizará en el caché como clave.

Supongo que te gusta

Origin blog.csdn.net/AA5279AA/article/details/123557335
Recomendado
Clasificación