Análisis de código fuente: comprensión del flujo de trabajo de EventBus

Análisis de código fuente de EventBus

EventBus es una biblioteca de código abierto que usamos con bastante frecuencia. Es relativamente simple de usar y poderosa. ¿Cómo no podemos explorar su implementación interna para una biblioteca tan poderosa?
## Creación y variables de
EventBus ### Crear objeto EventBus

Cuando usamos EventBus, a menudo usamos EventBus.getDefault (), usamos el método estático getDefault () para un objeto y luego realizamos operaciones como registrar y publicar. El método de construcción de la clase EventBus solo tiene una construcción sin parámetros externamente, y el método EventBus (constructor de EventBusBuilder) se llama internamente. En realidad, hay cuatro formas de obtener objetos EventBus:

  • ** EventBus.getDefault (): ** El método más utilizado, el modo singleton, utiliza la configuración predeterminada. Todas las llamadas en este método se utilizan en la aplicación para garantizar que el objeto obtenido sea único y no provocará que el evento de suscripción y el evento de envío se envíen sin el mismo objeto EventBus.
  • new EventBus (): menos utilizado, y también se utiliza la configuración predeterminada
  • EventBus.builder (). Build (): Construye un objeto a través del método Builder, que puede entenderse como un modo constructor y puede configurarse manualmente (impresión de registros, envío de eventos de excepción, lanzando excepciones, etc.)
  • ** EventBus.builder (). InstallDefaultEventBus (): ** También es un objeto construido en la forma de Builder, que se puede configurar manualmente. La diferencia con build () es que construye el objeto predeterminado de EventBus, que es el mismo que EventBus.getDefault ( ) Obtenga el mismo objeto. Nota: El método installDefaultEventBus () debe llamarse antes de EventBus.getDefault (); de lo contrario, el objeto se ha creado, la configuración no es válida y se lanza una excepción.

Variables de miembro de EventBus

Introduzca varias variables clave:

  • El objeto EventBuilder predeterminado, la configuración predeterminada
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
  • Variable clave, una colección de todos los objetos de eventos de suscripción (incluidos suscriptores y métodos de suscripción) distinguidos por tipos de eventos de suscripción, y todos los métodos de suscripción a un evento determinado se pueden encontrar
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
  • La variable clave, el suscriptor y su correspondiente colección de todos los eventos suscritos, pueden conocer todos los eventos suscritos por el suscriptor según el suscriptor
private final Map<Object, List<Class<?>>> typesBySubscriber
  • Una variable más importante, la clase PostingThreadState describe el estado de la publicación actual. Todos los eventos enviados a través del método ost se almacenarán primero en la cola de esta clase y luego se sacarán de la cola para las operaciones de distribución . Además, esta clase contiene variables como si se está distribuyendo, si la operación de publicación está en el hilo principal y si la operación se cancela.
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};
  • Controle por separado la distribución en el subproceso principal, la distribución del subproceso en segundo plano y las operaciones de distribución de subprocesos asincrónicos
private final Poster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
  • Encuentre todos los métodos de suscripción en un determinado suscriptor a través de este objeto
private final SubscriberMethodFinder subscriberMethodFinder;

Suscripción a EventBus

Registrarse()

Suscripción, es decir, el método register (), sabemos que después de llamar a EventBus.getDefault (). Register (this) en una clase, el evento de suscripción registrado se puede recibir en esa clase.

Antes de eso, primero entendemos dos clases:

** SubscriberMethod: ** Clase de método de suscriptor, que es el método que usamos @Subscribe anotado en nuestro código para monitorear y recibir métodos para procesar eventos.

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

** Suscripción: ** el objeto correspondiente al suscriptor y un método de evento en él

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

Continúe el proceso de registro:

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

Se puede ver que no hay muchas cosas escritas en este método. El primero es averiguar todos los métodos de suscripción a través del subscriptorMethodFinder antes mencionado. La implementación específica es averiguarlo mediante la reflexión. Luego, registre y vincule estos métodos respectivamente, así que continúe mirando el método 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) {
            ...
        }
    }

Este método es la clave para todo el proceso de registro, pero en términos simples, se puede resumir en dos puntos:
1. Obtenga una lista de todos los tipos de eventos registrados, inserte el método recién encontrado en la lista y finalmente use el tipo de evento como clave y la lista como El valor se actualiza o se inserta en la colección de mapas.
2. Obtenga una lista de todas las operaciones de suscripción relacionadas para este suscriptor, con el suscriptor como clave y la lista como valor, y colóquelas en la colección de mapas.

unRegister ()

Generalmente, el registro se cancela en el método onDestroy y el mensaje de recepción ya no se monitorea después de cancelar el registro. Desde el proceso de registro, podemos ver que en unRegister, solo necesitamos revertirlo ¿Cómo revertirlo? ¡Quitar, quitar, quitar!

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

## Monitoreo de eventos

Suscribir comentario

Cuando se usa la anotación @Subscribe, a veces se agrega un código como threadMode = ThreadMode.MAIN. ¿Por qué se usa? ¡Configure el hilo donde el oyente recibe el evento!

No solo eso, también podemos establecer si el evento es fijo o no, y establecer la prioridad. Suscribir notas:

@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

Esta es una configuración bastante impresionante en EventBus, que le permite especificar en qué hilo ejecutar la operación.

#### ThreadMode.POSTING
modo predeterminado. El evento se distribuirá directamente a los suscriptores después de la publicación y se ejecutará en el mismo hilo que el evento de envío . Debido a que se evita el cambio de hilo, la sobrecarga es mínima . Este también es el modo recomendado oficialmente, si su proyecto no implica demasiados cambios entre hilos. Pero trate de no realizar operaciones que consuman mucho tiempo en el método para evitar el bloqueo cuando el hilo de envío es el hilo principal .

#### ThreadMode.MAIN La supervisión y ejecución del
evento se llevará a cabo en el hilo principal (UI) . Si el evento de envío está en el hilo principal, se ejecutará directamente; de ​​lo contrario, el hilo principal se pondrá en cola y se distribuirá para su ejecución a través del cartel. Además , no realice operaciones que consuman mucho tiempo en el método para evitar bloquear el hilo principal .
#### ThreadMode.MAIN_ORDERED es
básicamente lo mismo que MAIN. La diferencia es que no importa desde qué hilo se envía el evento, debe ponerse en cola y ejecutarse uniformemente a través del hilo principal Poster. ,
#### ThreadMode.BACKGROUND
ejecución del hilo en segundo plano. Si el evento de envío no está en el hilo principal, se ejecutará directamente en el hilo actual; si el evento de envío está en el hilo principal, EventBus iniciará un hilo de fondo único y todos los eventos se pondrán en cola para su ejecución en este hilo. También es mejor no realizar operaciones que consuman mucho tiempo en los métodos.
#### ThreadMode.ASYNC
se ejecuta en un hilo asíncrono. Independientemente del subproceso que envía el evento, no se ejecutará en este subproceso. Se abre un nuevo subproceso asíncrono para realizar operaciones. Este modo se usa generalmente cuando es necesario realizar operaciones que consumen mucho tiempo en el monitor. EventBus utilizará el grupo de subprocesos para gestionar y evitar abrir demasiados subprocesos.

Enviar evento

Esto es más crítico. Solo necesita publicar una publicación simple cuando la use. Entonces, ¿cómo reciben los suscriptores estos eventos? ¿Y todavía quieres estar en qué hilo? De manera similar, primero comprendamos algunas categorías.

La
clase PendingPost se utiliza para describir un evento que se enviará y se mantiene una cola internamente para su reutilización.
Obtenga un objeto, reutilice la lista:

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

Suelte el objeto después de su uso:

    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 otras palabras, cada publicación que enviamos de un evento, eventualmente crearemos un objeto listo para enviar, que contiene el objeto del evento enviado, el tipo de evento, la información del suscriptor, el método de suscripción y otra información.

La
clase PendingPostQueue puede considerarse como una lista vinculada, y cada nodo es un objeto PendingPost, que está conectado por la siguiente variable de PendingPost. El valor y la eliminación pueden referirse al uso de la lista vinculada única.

### Iniciar publicación
1. Llame activamente al método post () para enviar un evento, pase un objeto Object
2. Obtenga el objeto PostingThreadState que mencionamos al principio, agregue el evento a la lista interna y juzgue el estado actual.

    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. De acuerdo con el tipo de evento, busque en la colección si hay un método de suscripción que se suscribe a este tipo de evento, si no lo encuentra, genere una excepción; si lo encuentra, procese cada operación de suscripción en un ciclo

            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. En el envío final, seleccione la distribución del póster correspondiente según el hilo seleccionado por el método de suscripción

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

llevado a cabo

La implementación es relativamente simple, usando el mecanismo de reflexión de 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);
        }
    }

Algunos carteles

En qué hilo se recibe el evento depende de qué Poster se use para enviarlo. El valor predeterminado es ejecutarlo directamente. El hilo principal se ejecuta usando HandlerPoster, la ejecución en segundo plano usa BackgroundPoster y la ejecución asincrónica usa AsyncPoster.
Primer vistazo a Poster, una clase de excusa para la implementación pública:

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

Hay un método de ejecución, que es enviar un evento.

AsyncPoster

Asynchronous Poster implementa las interfaces Runnable y Poster, y usa Runnable para abrir un nuevo hilo para su ejecución.
Cuando la puesta en cola se ejecute el método, el método run () que debe ser implementada en Ejecutable se ejecuta internamente. Se trata de un nuevo hilo de ejecución, y el método de reflexión invokeSubscriber () en EventBus se llama en el hilo.

La
diferencia entre BackgroundPoster y AsyncPoster es que solo abre un hilo. Cuando llega un evento y necesita ser distribuido, primero determina si se están realizando otras operaciones actualmente. Si está libre, ejecútelo inmediatamente, de lo contrario inserte la lista y la cola primero, y espere hasta que se procese el evento anterior. Luego emita el próximo evento.

HandlerPoster
es el póster del hilo principal al final.Cuando se crea este cartel, se pasa Looper.getMainLooper () , por lo que se da cuenta de la supervisión en el hilo principal. Este póster utiliza el modo Handler. Cuando recibe una solicitud para enviar un mensaje, llama a su propio método sendMessage () y luego procesa la solicitud en 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

En este punto, todo el proceso de EventBus básicamente ha terminado. Este artículo comienza con el análisis del proceso y básicamente explica todo el marco de EventBus y los principios de implementación interna. Hay muchas deficiencias en esto. No dude en aclararme.
¡gracias!

Supongo que te gusta

Origin blog.csdn.net/lizebin_bin/article/details/80409169
Recomendado
Clasificación