Interprétation du positionnement: un modèle de chaîne de responsabilité familier et inconnu des ingénieurs Android

0x00 Préface

En ce qui concerne le modèle de chaîne de responsabilité, vous penserez certainement à divers intercepteurs. Les intercepteurs sont utilisés dans de nombreux frameworks, tels que l'intercepteur de traitement des requêtes dans le okhttp commun et l'intercepteur de route dans le cadre de routage. Vous pouvez être l'utilisateur de Je me suis habitué aux intercepteurs, mais il est indéniable qu’un bon design fait souvent briller les gens. Cet article tente de résumer les scénarios d'utilisation et les méthodes de conception du mode chaîne de responsabilité dans le framework Android et le code source. 

0x01 Quel est le modèle de chaîne de responsabilité

Du point de vue des modèles de conception, la conception des intercepteurs est souvent appelée chaîne de responsabilité (chaîne de responsabilité). La définition du modèle de chaîne de responsabilité: afin d'éviter que l'expéditeur de la demande ne soit couplé à plusieurs gestionnaires de demandes, tous les gestionnaires de demandes sont liés dans une chaîne en se souvenant de la référence de l'objet suivant via l'objet précédent; lorsqu'une demande se produit time, la requête peut être transmise le long de cette chaîne jusqu'à ce qu'un objet la gère.

Avantages du modèle de chaîne de responsabilité:

  1. Pour réduire le couplage, il n'est pas nécessaire de connaître la structure de l'ensemble de la liaison de traitement, et l'émetteur et le récepteur n'ont pas besoin de connaître les informations exactes l'un de l'autre.
  2. Flexible et extensible, le flux de traitement peut être ajouté de manière flexible selon les besoins et la séquence de traitement peut être ajustée dynamiquement
  3. Cohésion des fonctions, responsabilité unique, chaque classe n'a besoin que de faire attention au travail qu'elle gère, et de passer au traitement d'objet suivant qui ne doit pas être traité

Inconvénients du modèle de chaîne de responsabilité:

  1. Complexité élevée, les performances peuvent être affectées
  2. Il n'y a aucune garantie que chaque demande sera traitée. Puisqu'une demande n'a pas de destinataire clair, il n'y a aucune garantie qu'elle sera traitée, et la demande peut ne pas être traitée jusqu'à ce qu'elle atteigne la fin de la chaîne.

Intercepteur d'appendice 0x02 dans okhttp

Il RealCall.javay a 5 intercepteurs intégrés. Créez une liste chaînée d'intercepteurs contenant tous les intercepteurs. Dans RealInterceptorChainla proceedméthode, récupérez l'intercepteur actuel en fonction de l'index, interceptappelez RealInterceptorChainla proceedméthode de manière récursive dans la méthode de l' intercepteur et mettez à jour index + 1 vers l'intercepteur suivant. Il convient de noter ici que l'implémentation du dernier intercepteur, dans le dernier intercepteur CallServerInterceptor, n'appellera pas la méthode procède de Chain, mais retournera directement la réponse basée sur le résultat du réseau. Cela peut être vu comme la fin de l'intercepteur entier, et après avoir atteint la fin de l'allée, la réponse est renvoyée étape par étape selon le chemin d'origine. Alors, un appel d'intercepteur qui ressemble à entrer dans une ruelle est-il considéré comme un demi-appendice?

/// 拦截器起点和终点
  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 Intercepteur d'oignon dans wmrouter

Par rapport à l'intercepteur okhttp, la différence de l'intercepteur dans wmrouter est qu'il n'a pas de valeur de retour de réponse et que les intercepteurs renvoient des résultats via des rappels ou interrompent le processus d'interception. La compréhension personnelle comprend principalement les considérations suivantes.

  • Il n'est pas nécessaire d'implémenter un intercepteur spécial à la fin de la file d'attente comme le fait okhttp. L'implémentation de tous les intercepteurs n'a besoin que de considérer si elle est suivante ou complète. De cette manière, la combinaison d'intercepteurs et l'imbrication sont plus flexibles.
  • Prise en charge du rappel d'interception asynchrone

Wmrouter utilise des intercepteurs imbriqués UriHandler et Interceptor multicouches pour rechercher des routes. Dans UriHandlerla handleméthode, shouldHandles'il est intercepté par le gestionnaire actuel, puis s'il contient un intercepteur pour déterminer s'il s'agit d'une interception de gestionnaire ou d'interception d'intercepteur, si elle est Intercepteur pour intercepter et entrer dans la couche suivante.
Donc, comparé à l'intercepteur okhttp, le mode intercepteur de wmrouter est plus stratifié et ressemble plus à un oignon.

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

Dans ChainedInterceptor, l'itération de l'intercepteur est mise à jour en fonction de l'appel récursif à la méthode suivante, et le rappel est utilisé pour remonter à la couche supérieure, où la couche supérieure peut être Interceptor ou 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();
        }
    }

Vous pouvez vous référer à la figure ci-dessous pour comprendre

Vous pouvez vous référer à l'organigramme dans le document officiel de wmrouter pour comprendre la relation d'imbrication à plusieurs niveaux entre ChainedHandler et ChainedInterceptor.

0x04 Interception de serpent distribuée par événement Touch

Vous devez avoir vu un organigramme de distribution d'événements comme celui-ci (à partir de https://www.gcssloop.com/customview/dispatch-touchevent-theory):

Dans la distribution d'écoulement dans trois méthode monotouche dispatchTouchEvent(), interceptTouchEvent(), touchEvent()représentant la distribution des événements, intercepteurs d'événements, consommateur d'événements. Vous devez avoir entendu le chemin de livraison d'événement en forme de U, en commençant par Activity, vers ViewGroup, puis vers View. Selon la valeur de retour de la méthode, trueil falseest déterminé si les trois méthodes ci-dessus distribuent, interceptent ou consomment des événements tactiles. Reportez-vous au pseudo code suivant pour comprendre,

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

Du point de vue de la chaîne de responsabilité, avez-vous déjà pensé à l'objectif ultime de l'ensemble du mécanisme dans tout le processus de distribution d'événements?

  • En fait, l'objectif de la distribution d'événements est de trouver la vue responsable de la consommation de l'ensemble du processus tactile. Le processus tactile comprend ici les événements DOWN, MOVE et UP. L'événement CANCEL peut être compris comme un signal d'erreur. L'événement de type CANCEL ne sera reçu que lorsque l'événement prédécesseur de la consommation de sous-vue, mais l'événement en cours est intercepté.
  • Les événements générés en une seule opération doivent être consommés par la même vue, tous reçus ou tous rejetés.
  • Par conséquent, il existe une règle dite de distribution selon laquelle les événements MOVE et UP ne sont acceptés qu'après la consommation des événements DOWN. Il peut être imaginé comme un serpent glouton composé d'événements DOWN, MOVE et UP. Le mouvement du corps et de la queue du serpent est basé sur la tête du serpent. Le mécanisme de distribution d'événements trouve d'abord le consommateur de l'événement DOWN, puis distribue les événements MOVE et UP en fonction de la cible touchTarget de l'événement DOWN.
  • Le transfert dit en forme de U, de ViewGroup à View, et de View à ViewGroup, uniquement les événements de type DOWN possibles

Lorsque l'écouteur d'événement de clic est défini pour le parent et l'enfant en même temps, pourquoi l'enfant répond-il en premier?
Étant donné que l'événement click reçoit une réponse dans onTouchEvent et que l'ordre de consommation de onTouchEvent est d'abord enfant, puis parent. Lorsque la vue est définie avec touchListener ou clickListener, l'événement est intercepté par la vue.

Résumé 0x05

  • Dans la conception et la mise en œuvre de la chaîne de responsabilité, la demande ou l'événement est passé du premier nœud au bas à son tour via une liste chaînée ou un appel récursif, et un retour en arrière
  • À n'importe quel nœud de la chaîne de responsabilité, vous pouvez décider de mettre fin à la livraison d'événements sur la chaîne de responsabilité en fonction de la situation, okhttp se termine en lançant une exception et wmroutor quitte la pile de manière séquentielle via un rappel

 

Je suppose que tu aimes

Origine blog.csdn.net/qq_39477770/article/details/112404161
conseillé
Classement