Analyse du code source - Compréhension du flux de travail EventBus

Analyse du code source EventBus

EventBus est une bibliothèque open source que nous utilisons assez fréquemment. Elle est relativement simple à utiliser et puissante. Comment ne pas explorer son implémentation interne pour une bibliothèque aussi puissante?
## Création et variables d'
EventBus ### Créer un objet EventBus

Lorsque nous utilisons EventBus, nous utilisons souvent EventBus.getDefault (), utilisons la méthode statique getDefault () pour un objet, puis effectuons des opérations telles que s'inscrire et publier. La méthode de construction de la classe EventBus a uniquement une construction sans paramètre en externe, et la méthode EventBus (EventBusBuilder builder) est appelée en interne. Il existe en fait quatre façons d'obtenir des objets EventBus:

  • ** EventBus.getDefault (): ** La méthode la plus couramment utilisée, le mode singleton, utilise la configuration par défaut. Tous les appels de cette méthode sont utilisés dans l'application pour garantir que l'objet obtenu est unique et que l'événement d'abonnement et l'événement d'envoi ne seront pas envoyés sans le même objet EventBus.
  • new EventBus (): moins utilisé, et la configuration par défaut est également utilisée
  • EventBus.builder (). Build (): Construisez un objet via la méthode Builder, qui peut être comprise comme un mode générateur et peut être configurée manuellement (impression du journal, envoi d'événements d'exception, levée d'exceptions, etc.)
  • ** EventBus.builder (). InstallDefaultEventBus (): ** C'est également un objet construit à la manière de Builder, qui peut être configuré manuellement. La différence avec build () est qu'il construit l'objet par défaut d'EventBus, qui est le même que EventBus.getDefault ( ) Obtenez le même objet. Remarque: La méthode installDefaultEventBus () doit être appelée avant EventBus.getDefault (), sinon l'objet a été créé, la configuration n'est pas valide et une exception est levée.

Variables de membre EventBus

Introduisez plusieurs variables clés:

  • L'objet EventBuilder par défaut, la configuration par défaut
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
  • Variable clé, une collection de tous les objets d'événement d'abonnement (y compris les abonnés et les méthodes d'abonnement) distingués par les types d'événement d'abonnement, et toutes les méthodes d'abonnement à un certain événement peuvent être trouvées
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
  • La variable clé, l'abonné et sa collection correspondante de tous les événements souscrits, peuvent connaître tous les événements souscrits par l'abonné en fonction de l'abonné
private final Map<Object, List<Class<?>>> typesBySubscriber
  • Une variable plus importante, la classe PostingThreadState décrit l'état de la publication actuelle. Tous les événements envoyés via la méthode ost seront d'abord stockés dans la file d'attente de cette classe, puis retirés de la file d'attente pour les opérations de distribution . En outre, cette classe contient des variables telles que si elle est en cours de distribution, si l'opération de publication est dans le thread principal et si l'opération est annulée.
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};
  • Contrôlez séparément la distribution dans le thread principal, la distribution des threads d'arrière-plan et les opérations de distribution des threads asynchrones
private final Poster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
  • Rechercher toutes les méthodes d'abonnement chez un certain abonné via cet objet
private final SubscriberMethodFinder subscriberMethodFinder;

Abonnement EventBus

S'inscrire()

Subscription, c'est-à-dire la méthode register (), nous savons qu'après avoir appelé EventBus.getDefault (). Register (this) dans une classe, l'événement d'abonnement enregistré peut être reçu dans cette classe.

Avant cela, nous comprenons d'abord deux classes:

** SubscriberMethod: ** Classe de méthode Subscriber, qui est la méthode que nous utilisons @Subscribe annotée dans notre code pour surveiller et recevoir des méthodes de traitement des événements.

final Method method;         //方法名、参数等信息,android原生反射类
final ThreadMode threadMode; //在哪一个线程中接收此事件
final Class<?> eventType;    //事件类型,如String.class等
final int priority;          //优先级,
final boolean sticky;        //是否粘性传输
String methodString;         //用来检测是否是同一个监听方法

** Abonnement: ** l'objet correspondant à l'abonné et une méthode événementielle qu'il contient

final Object subscriber;                //订阅者(例如某一个Activity对象)
final SubscriberMethod subscriberMethod;//订阅者内的一个订阅方法

Continuez le processus d'inscription:

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        //找出这个类监听的所有事件,遍历列表进行订阅相关操作
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

On peut voir qu'il n'y a pas beaucoup de choses écrites dans cette méthode. La première est de découvrir toutes les méthodes d'abonnement via le subscriptionMethodFinder mentionné ci-dessus. La mise en œuvre spécifique est de découvrir par la réflexion. Ensuite, enregistrez et liez ces méthodes respectivement, alors continuez à regarder la méthode subscribe ().

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
	    //获取该订阅方法的事件类型
        Class<?> eventType = subscriberMethod.eventType;
        //创建一个新的订阅者和订阅方法组合对象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //根据事件类型,获取当前所有监听该类型的订阅操作(订阅者和订阅方法)
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        //如果找不到,新建列表,以当前事件类型为key塞入map集合中
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //同一个订阅者对象中,如果有该订阅方法,不能重复订阅,也就是一个类中的onEvent方法不能是相同的参数(事件类型)
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        int size = subscriptions.size();
        //将新的订阅方法插入到该事件类型对应的集合中(插入到最后,或者定义了优先级的话,根据优先级插入)
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        //获取该订阅者订阅的所有事件,如果没有,新插入一个
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        //将订阅的事件插入到该订阅者订阅的所有事件的集合中
        subscribedEvents.add(eventType);

        //粘性事件处理
        if (subscriberMethod.sticky) {
            ...
        }
    }

Cette méthode est la clé de l'ensemble du processus de registre, mais en termes simples, elle peut être résumée en deux points:
1. Obtenez une liste de tous les types d'événements enregistrés, insérez la méthode nouvellement trouvée dans la liste, et enfin utilisez le type d'événement comme clé et la liste comme La valeur est mise à jour ou insérée dans la collection de cartes.
2. Obtenez une liste de toutes les opérations d'abonnement associées pour cet abonné, avec l'abonné comme clé et la liste comme valeur, et insérez-les dans la collection de cartes.

désenregistrer ()

En règle générale, l'enregistrement est annulé dans la méthode onDestroy et le message de réception n'est plus surveillé après l'annulation de l'enregistrement. À partir du processus d'enregistrement, nous pouvons voir que dans unRegister, il suffit de l'inverser. Comment l'inverser? Supprimer, supprimer, supprimer!

    public synchronized void unregister(Object subscriber) {
        //找出这个订阅者订阅的所有事件
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
	        //遍历这些事件方法,在事件类型-订阅操作map集合中找出这些方法并移除
            for (Class<?> eventType : subscribedTypes) {
	            //方法实现为:找出这个事件类型的所有订阅操作,循环匹配如果是当前这个订阅者订阅的,从列表中移除
                unsubscribeByEventType(subscriber, eventType);
            }
            //最后从map集合中移除这个订阅者
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

## Surveillance des événements

Abonnez-vous au commentaire

Lorsque vous utilisez l'annotation @Subscribe, un code tel que threadMode = ThreadMode.MAIN est parfois ajouté. Pourquoi est-il utilisé? Configurez le fil de discussion où l'auditeur reçoit l'événement!

Non seulement cela, nous pouvons également définir si l'événement est permanent ou non, et définir la priorité. Souscrire des notes:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
	//可设置线程模式,默认为POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
	//是否粘性传输,默认false
    boolean sticky() default false;
	//优先级,默认为0,排到队尾
    int priority() default 0;
}

ThreadMode

Il s'agit d'un paramètre assez impressionnant dans EventBus, qui vous permet de spécifier dans quel thread exécuter l'opération.

#### ThreadMode.POSTING
mode par défaut. L'événement sera directement distribué aux abonnés après la publication et sera exécuté dans le même thread que l'événement d'envoi . Comme le changement de thread est évité, la surcharge est minime . C'est également le mode officiellement recommandé, si votre projet n'implique pas trop de basculement entre les threads. Mais essayez de ne pas effectuer d'opérations chronophages dans la méthode pour éviter le blocage lorsque le thread d'envoi est le thread principal .

#### La surveillance et l'exécution des
événements ThreadMode.MAIN seront effectuées dans le thread principal (UI) . Si l'événement d'envoi est dans le thread principal, il sera exécuté directement, sinon, le thread principal sera mis en file d'attente et distribué pour exécution via le poster. Aussi ne pas effectuer des opérations qui prennent du temps dans la méthode pour éviter de bloquer le thread principal .
#### ThreadMode.MAIN_ORDERED est
fondamentalement identique à MAIN. La différence est que quel que soit le thread à partir duquel l'événement est envoyé, il doit être mis en file d'attente et exécuté uniformément via le thread principal Poster. ,
#### ThreadMode.BACKGROUND
exécution du thread d'arrière-plan. Si l'événement d'envoi n'est pas dans le thread principal, il sera exécuté directement dans le thread actuel; si l'événement d'envoi se trouve dans le thread principal, EventBus démarrera un thread d'arrière-plan unique et tous les événements seront mis en file d'attente pour exécution dans ce thread. Il est également préférable de ne pas effectuer d'opérations chronophages dans les méthodes.
#### ThreadMode.ASYNC
est exécuté dans un thread asynchrone. Quel que soit le thread qui envoie l'événement, il ne sera pas exécuté dans ce thread. Un nouveau thread asynchrone est ouvert pour effectuer des opérations. Ce mode est généralement utilisé lorsque vous devez effectuer des opérations chronophages dans le moniteur. EventBus utilisera le pool de threads pour éviter d'ouvrir trop de threads.

Envoyer l'événement

C'est plus critique. Vous n'avez besoin de publier un message que lorsque vous l'utilisez. Comment les abonnés reçoivent-ils ces événements? Et tu veux toujours être dans quel fil? De même, commençons par comprendre quelques catégories.

La
classe PendingPost est utilisée pour décrire un événement à envoyer et une file d'attente est gérée en interne pour une réutilisation.
Récupérez un objet, réutilisez la liste:

    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        /**
         * 如果当前正在从队列中复用,则直接return一个new出来的对象
         * 如果队列为空,同样返回新对象
         * 如果能够复用,则取队列最后一个对象,将其变量置为传入的参数,最后return这个对象
         */
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            if (size > 0) {
                //取对象使用的是remove方法,从队列中移出,这样保证队列不会越来越长
                PendingPost pendingPost = pendingPostPool.remove(size - 1);
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                return pendingPost;
            }
        }
        return new PendingPost(event, subscription);
    }

Relâchez l'objet après utilisation:

    static void releasePendingPost(PendingPost pendingPost) {
        pendingPost.event = null;
        pendingPost.subscription = null;
        pendingPost.next = null;
        synchronized (pendingPostPool) {
            // Don't let the pool grow indefinitely
            /**
             * 当前队列不能超过10000,因为每一次obtain要么获取一个新对象,要么从队列中移除最后一个对象,
             * 所以即是保证当前同一个线程同一时间内需要发送的事件操作不超过10000
             */
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }

En d'autres termes, à chaque publication que nous envoyons un événement, nous créerons éventuellement un tel objet prêt à envoyer, qui contient l'objet événement envoyé, le type d'événement, les informations sur l'abonné, la méthode d'abonnement et d'autres informations.

La
classe PendingPostQueue peut être considérée comme une liste liée et chaque nœud est un objet PendingPost, qui est connecté par la variable suivante de PendingPost. La valeur et la suppression peuvent faire référence à l'utilisation de la liste liée unique.

### Start Post
1. Appelez activement la méthode post () pour envoyer un événement, passez un objet Object
2. Récupérez l'objet PostingThreadState que nous avons mentionné au début, ajoutez l'événement à la liste à l'intérieur et jugez l'état actuel.

    public void post(Object event) {
        /**
         * 获取当前发送对象,添加到队列中,如果正在发送其他事件,则在发送后会继续发送新加入的事件(while循环)
         * 如果当前空闲,则立即发送事件
         * 最后重置状态
         */
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            //更改当前状态
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                //从队列中获取事件对象使用remove方法获取,省去发送后从队列删除这一操作
                //列表为空时退出循环,因为前面的逻辑,是有可能正在post操作的时候,新的post发过来了,添加到了列表中,
                //但是还没有没有真正发送,所以发送完一个之后需要判断是否还有需要发送的事件
                while (!eventQueue.isEmpty()) {
		            //真正发送在这里
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

3. Selon le type d'événement, recherchez dans la collection s'il existe une méthode d'abonnement qui s'abonne à ce type d'événement, si elle n'est pas trouvée, lancez une exception; si elle est trouvée, traitez chaque opération d'abonnement en boucle.

            for (Subscription subscription : subscriptions) {
	            //当前发送状态更新
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
	                //给订阅者发送事件
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }

5. Lors de l'envoi final, sélectionnez la distribution d'affiche correspondante en fonction du fil de discussion sélectionné par la méthode d'abonnement

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        //当初注册@Subscribe注解时设置的在哪个线程中接收事件,默认Posting,即当前线程(和发送者同一线程)
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
	            //直接当前线程执行
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
	            //如果当前是主线程,直接执行
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                //否则使用主线程Poster统一排队分发
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
	            //不管处于何线程,统一使用主线程Poster排队分发
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
	            //如果不在主线程,直接执行,否则,使用后台Poster排队分发
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
	            //不管处于何线程,统一使用异步Poster排队分发
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

effectué

La mise en œuvre est relativement simple, en utilisant le mécanisme de réflexion Java

    void invokeSubscriber(Subscription subscription, Object event) {
        try {
	        //执行方法的类对象,参数
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

Quelques affiches

Le thread dans lequel l'événement est reçu dépend du Poster utilisé pour l'envoyer. Par défaut, il est exécuté directement. Le thread principal s'exécute à l'aide de HandlerPoster, l'exécution en arrière-plan utilise BackgroundPoster et l'exécution asynchrone utilise AsyncPoster.
Premier regard sur Poster, une classe d'excuse pour la mise en œuvre publique:

interface Poster {
    void enqueue(Subscription subscription, Object event);
}

Il existe une méthode d'exécution, qui consiste à envoyer un événement

AsyncPoster

Asynchronous Poster implémente les interfaces Runnable et Poster, et utilise Runnable pour ouvrir un nouveau thread pour exécution.
Lorsque la méthode de mise en file d'attente est exécutée, la méthode run () qui doit être implémentée dans Runnable est exécutée en interne. Il s'agit d'un nouveau thread à exécuter et la méthode de réflexion invokeSubscriber () d'EventBus est appelée dans le thread.

La
différence entre BackgroundPoster et AsyncPoster est qu'il n'ouvre qu'un seul thread. Lorsqu'un événement arrive et doit être distribué, il détermine d'abord si d'autres opérations sont en cours d'exécution. S'il est gratuit, exécutez-le immédiatement, sinon insérez d'abord la liste et la file d'attente, et attendez que l'événement précédent soit traité. Ensuite, lancez l'événement suivant.

HandlerPoster
est le thread principal Poster à la fin. Quand ce Poster est créé, Looper.getMainLooper () est passé , donc il réalise la surveillance sur le thread principal. Ce Poster utilise le mode Handler. Lorsqu'il reçoit une demande d'envoi d'un message, il appelle sa propre méthode sendMessage (), puis traite la demande dans handleMessage.

    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            //开始处理时间
            long started = SystemClock.uptimeMillis();
            while (true) {
	            //从队列中取出一个PendingPost操作
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                //最终的使用反射执行订阅方法
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                //如果循环执行时间大于10毫秒,退出循环,避免一直从队列中取数据,
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }

Fin

À ce stade, tout le processus EventBus est pratiquement terminé. Cet article part de l'analyse du processus et explique en gros l'ensemble du framework EventBus et les principes de mise en œuvre interne. Il y a de nombreuses lacunes à cet égard. N'hésitez pas à m'éclairer.
remercier!

Je suppose que tu aimes

Origine blog.csdn.net/lizebin_bin/article/details/80409169
conseillé
Classement