Interpretación de posicionamiento: un modelo de cadena de responsabilidad que es familiar y desconocido para los ingenieros de Android

0x00 Prefacio

Cuando se trata del modelo de cadena de responsabilidad, definitivamente pensará en varios interceptores. Los interceptores se utilizan en muchos marcos, como el interceptor de procesamiento de solicitudes en el okhttp común y el interceptor de ruta en el marco de enrutamiento. Puede ser el usuario de Me he acostumbrado a los interceptores, pero es innegable que un buen diseño suele hacer brillar a la gente. Este artículo intenta resumir los escenarios de uso y los métodos de diseño del modo de cadena de responsabilidad en el marco y el código fuente de Android. 

0x01 ¿Qué es el modelo de cadena de responsabilidad?

Desde la perspectiva de los patrones de diseño, el diseño de interceptores a menudo se denomina Cadena de responsabilidad (Cadena de responsabilidad). La definición del patrón de cadena de responsabilidad: para evitar que el remitente de la solicitud se asocie con múltiples manejadores de solicitudes, todos los manejadores de solicitudes se vinculan en una cadena recordando la referencia del siguiente objeto a través del objeto anterior; cuando ocurre una solicitud En el tiempo, la solicitud se puede pasar a lo largo de esta cadena hasta que un objeto la maneje.

Ventajas del modelo de cadena de responsabilidad:

  1. Para reducir el acoplamiento, no es necesario conocer la estructura de todo el enlace de procesamiento, y el remitente y el receptor no necesitan conocer la información exacta el uno del otro.
  2. Flexible y extensible, el flujo de procesamiento se puede agregar de manera flexible según sea necesario, y la secuencia de procesamiento se puede ajustar dinámicamente
  3. Cohesión de funciones, responsabilidad única, cada clase solo necesita prestar atención al trabajo que maneja y pasar al siguiente procesamiento de objetos que no deben procesarse

Desventajas del modelo de cadena de responsabilidad:

  1. Alta complejidad, el rendimiento puede verse afectado
  2. No hay garantía de que se procesen todas las solicitudes. Dado que una solicitud no tiene un destinatario claro, no hay garantía de que se procese y es posible que la solicitud no se procese hasta que llegue al final de la cadena.

0x02 interceptor de apéndice en okhttp

Hay RealCall.java5 interceptores integrados. Cree una lista enlazada de interceptores que contenga todos los interceptores. En RealInterceptorChainel proceedmétodo, obtenga el interceptor actual basado en el índice, interceptllame RealInterceptorChainal proceedmétodo de forma recursiva dentro del método del interceptor y actualice el índice + 1 al siguiente interceptor. Cabe señalar aquí que la implementación del último interceptor, en el último interceptor CallServerInterceptor, no llamará al método de proceder de Chain, sino que devolverá directamente la respuesta basada en el resultado de la red. Esto puede verse como el final de todo el interceptor, y después de llegar al final del callejón, la respuesta se devuelve paso a paso de acuerdo con la ruta original. Entonces, ¿una llamada de interceptor que parece entrar en un callejón se cuenta como medio apéndice?

/// 拦截器起点和终点
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    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));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }


/// 拦截器迭代
  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;
  }


/// 单个拦截器递归回调到拦截器链
public Response intercept(Chain chain) throws IOException {
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    return response;
  }

0x03 Interceptor de cebolla en wmrouter

En comparación con el interceptor de okhttp, la diferencia del interceptor en wmrouter es que no tiene un valor de retorno de respuesta, y los interceptores devuelven resultados a través de devoluciones de llamada o interrumpen el proceso de interceptación. La comprensión personal incluye principalmente las siguientes consideraciones.

  • No es necesario implementar un interceptor especial al final de la cola como lo hace okhttp. La implementación de todos los interceptores solo necesita considerar si es el siguiente o está completo. De esta forma, la combinación de interceptores y el anidamiento son más flexibles.
  • Admite devolución de llamada de intercepción asincrónica

Wmrouter utiliza interceptores anidados UriHandler e Interceptor multicapa en el proceso de búsqueda de rutas. En UriHandlerel handlemétodo, shouldHandlesi es interceptado por el Handler actual y luego si contiene un interceptor para determinar si es una interceptación de handler o interceptor de interceptor, si es Interceptor para interceptar y entrar en la siguiente capa.
Entonces, en comparación con el interceptor de okhttp, el modo de interceptor de wmrouter tiene más capas y se parece más a una cebolla.

public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
    if (shouldHandle(request)) {
        if (mInterceptor != null && !request.isSkipInterceptors()) {
            mInterceptor.intercept(request, new UriCallback() {
                @Override
                public void onNext() {
                    handleInternal(request, callback);
                }

                @Override
                public void onComplete(int result) {
                    callback.onComplete(result);
                }
            });
        } else {
            handleInternal(request, callback);
        }
    } else {
        callback.onNext();
    }
}

En ChainedInterceptor, la iteración del interceptor se actualiza de acuerdo con la llamada recursiva al siguiente método, y la devolución de llamada se usa para rastrear hasta la capa superior, donde la capa superior puede ser Interceptor o UriHandler.

public class ChainedInterceptor implements UriInterceptor {

    private void next(@NonNull final Iterator<UriInterceptor> iterator, @NonNull final UriRequest request,
                      @NonNull final UriCallback callback) {
        if (iterator.hasNext()) {
            UriInterceptor t = iterator.next();
            t.intercept(request, new UriCallback() {
                @Override
                public void onNext() {
                    next(iterator, request, callback);
                }

                @Override
                public void onComplete(int resultCode) {
                    callback.onComplete(resultCode);
                }
            });
        } else {
            callback.onNext();
        }
    }

Puede consultar la figura siguiente para comprender

Puede consultar el diagrama de flujo en el documento oficial de wmrouter para comprender la relación de anidamiento de múltiples niveles entre ChainedHandler y ChainedInterceptor.

0x04 Intercepción de serpientes distribuida por evento Touch

Debe haber visto un diagrama de flujo de distribución de eventos como este (de https://www.gcssloop.com/customview/dispatch-touchevent-theory):

En la distribución de flujo en tres método de pulsación de teclas dispatchTouchEvent(), interceptTouchEvent(), touchEvent()que representa la distribución evento, interceptor acontecimiento, acontecimiento consumidor. Debe haber escuchado la llamada ruta de entrega de eventos en forma de U, comenzando desde Actividad, hasta ViewGroup y luego hasta View. De acuerdo con el valor de retorno del método, truese falsedetermina si los tres métodos anteriores distribuyen, interceptan o consumen eventos táctiles. Consulte el siguiente pseudocódigo para comprender,

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 默认状态为没有消费过
    boolean result = false;             

    if (!onInterceptTouchEvent(ev)) {
        // 如果没有拦截就直接分发给子View
        result = child.dispatchTouchEvent(ev);
    }

    if (!result) {
        // 如果事件没有子View消费或者是被自己拦截了,询问自身onTouchEvent
        result = onTouchEvent(ev);
    }

    return result;
}

Desde la perspectiva de la cadena de responsabilidad, ¿alguna vez ha pensado en el objetivo final de todo el mecanismo en todo el proceso de distribución de eventos?

  • De hecho, el objetivo de la distribución de eventos es encontrar la vista responsable de consumir todo el proceso táctil. El proceso táctil aquí incluye eventos DOWN, MOVE y UP. El evento CANCEL puede entenderse como una señal de error, sólo cuando el evento predecesor del consumo de la subvista, pero el evento actual sea interceptado, se recibirá el evento tipo CANCEL.
  • Los eventos generados en un proceso de un toque deben ser consumidos por la misma Vista, todos recibidos o todos rechazados.
  • Por lo tanto, existe una regla de distribución que establece que los eventos MOVE y UP se aceptan solo después de que se consumen los eventos DOWN. Se puede imaginar como una serpiente glotona compuesta de eventos DOWN, MOVE y UP. El movimiento del cuerpo y la cola de la serpiente se basa en la cabeza de la serpiente. El mecanismo de distribución de eventos primero encuentra al consumidor del evento DOWN y luego distribuye los eventos MOVE y UP de acuerdo con el touchTarget del evento DOWN.
  • La llamada transferencia en forma de U, de ViewGroup a View, y de View a ViewGroup, transferencia de eventos, solo posibles eventos de tipo DOWN

Cuando el detector de eventos de clic está configurado para padre e hijo al mismo tiempo, ¿por qué el hijo responde primero?
Debido a que el evento click se responde en onTouchEvent, y el orden de consumo de onTouchEvent es hijo primero y luego padre. Cuando la vista se establece con touchListener o clickListener, el evento será interceptado por la vista.

0x05 resumen

  • En el diseño e implementación de la cadena de responsabilidad, la solicitud o evento se pasa del primer nodo al final a su vez a través de una lista enlazada o llamada recursiva y retroceso
  • En cualquier nodo de la cadena de responsabilidad, puede decidir si finalizar la entrega del evento en la cadena de responsabilidad según la situación, okhttp termina lanzando una excepción y wmroutor sale de la pila secuencialmente a través de la devolución de llamada.

 

Supongo que te gusta

Origin blog.csdn.net/qq_39477770/article/details/112404161
Recomendado
Clasificación