EventBus3.0阅读笔记

简述

很多时候有的业务隔离的很严重,但是有需要在一定的时机上互相通知,为了解决这个场景有一种比较合适的方案,就是事件总线机制。EventBus就是该机制的一种实现,基于观察者模式,简单说就是有一个总线控制,然后观察者挂载到总线上,然后有消息发送出来之后,总线根据挂载情况进行分发。
以下代码基于EventBus3.0

参数和构造

先看一下EventBus基础的参数和构造:

    //DCL单例模式
    static volatile EventBus defaultInstance;

    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
    //原始事件和该事件可以进行回调的其他事件列表的集合
    //一般来说就是结合eventInheritance
    //然后会记录自己的所有父类和所实现的所有接口(包括每一个父类所实现的接口)等
    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
    //回调的方法对应的参数(事件)和对应的订阅者单元(订阅的类和具体的某一个方法)所构成的集合
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    //订阅者(类)和其所对应的所有事件的列表
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    //黏性事件的对象所对应的类和对象所构成的集合
    private final Map<Class<?>, Object> stickyEvents;
    //每一个线程都有自己对应的发送状态等参数
    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
    //用于在主线程进行事件的回调处理方法
    private final HandlerPoster mainThreadPoster;
    //用于在EventBus的线程池即executorService中的子线程进行事件的回调处理
    //内部的实现会在执行队列非空的情况下使用单一的线程一直执行,并且规定了最大等待(空闲)时间
    //只有在超过该空闲时间之后,再进行任务,才有可能使用其他线程
    //不过默认使用的是cacheExecutor,这意味着至少还有60s的空闲时间,在此期间也是能够继续复用线程的
    private final BackgroundPoster backgroundPoster;
    //用于在EventBus的线程池即executorService中的子线程进行事件的回调处理
    //区别于BackgroundPoster来说这个post是单纯通过线程池调度
    private final AsyncPoster asyncPoster;
    //用于查找所有观察者用于回调处理事件的方法
    private final SubscriberMethodFinder subscriberMethodFinder;
    //线程池,用于处理在子线程中进行的回调
    private final ExecutorService executorService;

    //当某一个事件在发送的时候有观察者处理,但是在进行函数回调的时候出现InvocationTargetException异常
    //此时是否抛出异常,如果抛出异常,那么sendSubscriberExceptionEvent和logSubscriberExceptions直接就无效了
    private final boolean throwSubscriberException;
    //当某一个事件在发送的时候有观察者处理,但是在进行函数回调的时候出现InvocationTargetException异常
    //此时是否要打印日志
    private final boolean logSubscriberExceptions;
    private final boolean logNoSubscriberMessages;
    //当某一个事件在发送的时候有观察者处理,但是在进行函数回调的时候出现InvocationTargetException异常
    //是否需要额外的发送一个预先定义的SubscriberExceptionEvent事件
    private final boolean sendSubscriberExceptionEvent;
    //当某一个事件在发送的时候没有观察者处理
    //是否需要额外的发送一个预先定义的NoSubscriberEvent事件,这个事件会记录的之前那个没有被处理的事件
    private final boolean sendNoSubscriberEvent;
    //是否处理子类事件,如果为true的时候,假设有一个事件A发送出去
    //而某一个观察者中的处理事件可能为A的父类,那么此时也会进行事件回调
    private final boolean eventInheritance;
    //Builder中注册的索引数量
    private final int indexCount;

    /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

EventBus为单例模式,也就是一个总线控制台。可以看到实际上观察者都是在内存中的。

观察者的注册

    /**
     * 注册一个观察者用于接收指定的一些事件,实际上就是查找回调方法,然后记录一堆数据
     * 当观察者不再关心这些事件的时候,要通过unregister注销,从而使得观察者不再接收那些事件的回调
     * 观察者必须要有回调方法,且该回调方法必须通过@Subscribe进行注解
     * 该注解还可以指定线程和优先级
     */
    public void register(Object subscriber) {
        //获得观察者的类,比方说在AFragment中注册,那么这里就会获得AFragment这个类
        Class<?> subscriberClass = subscriber.getClass();
        //通过观察者类查找里面定义的所有回调方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {//这个只是对象锁,这就意味着不同的EventBus实例,事件的回调不会互相上锁
            //为了防止处理过程中列表变化,这里必须上锁
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

    /**
     * 将当前观察者和对应的方法记录在缓存当中,相当于标记注册
     * 不允许重复添加!所以说重复添加之前必须进行反注册或者清除缓存
     * 如果当前观察者进行回调的方法处理黏性事件,那么需要尝试进行黏性事件回调
     * @param subscriber 当前的观察者
     * @param subscriberMethod 当前的观察者所定义的某一个具体的事件回调方法
     */
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //获得当前方法对应的事件,实际上就是观察者所观察的某一个事件
        Class<?> eventType = subscriberMethod.eventType;
        //通过观察者和该方法定义一个个新的观察单元
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //从缓存中通过当前事件获得观察单元
        //同一个事件当然可以有不同的观察者
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            //当前事件没有观察单元处理,新建
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //其实就是不允许同一个类重复注册,要重新注册,那么之前必须反注册
            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) {//当前观察者的回调方法是处理黏性事件的
            if (eventInheritance) {
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {//遍历黏性事件缓存
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        //如果当前观察者的回调方法处理的事件类型为缓存中某一个黏性事件的父类
                        //举例说明当前方法处理的黏性事件为A
                        //之前已经有黏性事件B发送过了,而且满足B extends A
                        //那么当前B事件也会进行回调处理
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                //从缓存中通过当前事件来获取黏性事件,如果在这之前已经有黏性事件发送,那么缓存中必定有值
                Object stickyEvent = stickyEvents.get(eventType);
                //如果缓存中有值,即null != stickyEvent,说明之前该黏性事件已经发送过了,尝试进行回调
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

    /**
     * 检查当前黏性事件是否为空
     * 非空则进行事件回调处理
     * @param newSubscription 订阅元素(当前的观察者和其处理当前黏性事件的方法)
     * @param stickyEvent 当前可能需要进行回调的黏性事件
     */
    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
        }
    }

实际上就是记录了当前观察者和观察者想要观察的事件、回调方法。
这个从上面的参数中也可以看到:
1.观察者和该观察者所关心的事件列表所构成的映射,这个一般用于判断某一个观察者是否注册。
2.事件和可以处理该事件的观察者、回调方法所构成的映射,这个用于在发送事件的时候快速查找对应的回调。其中该队列有按照优先级排序,后续在回调的时候可以看到是正序遍历的,所以说优先级高的会先回调。
其中稍微注意一下黏性事件,简单的理解就是黏性事件一旦发送,哪怕观察者在后面注册,也会收到该事件。

事件发送

    /**
     * 发送一个黏性事件
     * 这个事件会保留在内存当中,然后如果发送之后还有黏性事件的观察者注册了
     * 此时该事件也可能会进行回调处理
     */
    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);//黏性事件会保留在内存当中
        }
        //当前先发送事件给之前注册了当前事件的观察者
        post(event);
    }

    /**
     * 发送一个事件到事件总线上,并且尝试进行对应的回调
     * @param event 当前需要发送的事件
     * */
    public void post(Object event) {
        //通过当前调用的线程获得对应的PostingThreadState
        PostingThreadState postingState = currentPostingThreadState.get();
        //获取当前线程的事件队列
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {//当前线程未处于事件发送中,开始进行事件的发送
            //记录当前线程是否为主线程
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            //标记当前线程开始发送事件
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {//会尝试将当前队列中的所有事件都发送出去
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                //事件发送完成
                postingState.isPosting = false;//当前未处于发送状态
                postingState.isMainThread = false;//每次发送的时候都会重置
            }
        }
    }

    /**
     * 尝试发送单个事件
     * @param event 当前需要发送的事件
     * @param postingState 当前线程的发送状态等参数
     */
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {//如果当前事件是一些观察者所观察的事件的子类,那么也进行回调
            //查找当前事件所有的父类和接口
            //这些父类和接口的观察者都会接收到当前事件
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                //将当前事件event发送给那些父类、接口、自身的观察者
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            //单纯将当前事件event发送给当前event的观察者
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {//当前事件没有人处理
            if (logNoSubscriberMessages) {
                Log.d(TAG, "No subscribers registered for event " + eventClass);
            }
            //当前事件不能为预定义处理的两个事件
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                //当前允许发送NoSubscriberEvent事件,重新走发送流程
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

    /**
     * 在eventInheritance为true的基础上
     * 查找原始事件的父类和接口,这意味着如果有观察者观察这些父类和接口,
     * 那么对于这个原始事件来说他们也是观察的
     * @param eventClass 原始事件
     * @return 当前事件实际可以处理的事件集合,包括子类和接口
     */
    private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
        synchronized (eventTypesCache) {
            List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
            if (eventTypes == null) {
                eventTypes = new ArrayList<>();
                Class<?> clazz = eventClass;
                while (clazz != null) {//遍历
                    eventTypes.add(clazz);//首先是记录当前原始事件,如果有后续循环的话会处理会处理父类
                    //getInterfaces会返回当前类所实现的所有接口
                    addInterfaces(eventTypes, clazz.getInterfaces());
                    clazz = clazz.getSuperclass();
                }
                eventTypesCache.put(eventClass, eventTypes);
            }
            return eventTypes;
        }
    }

    /**
     * 通过制定的观察者所观察的事件和当前发送的事件
     * @param event 当前发送的事件
     * @param postingState 当前线程的发送状态
     * @param eventClass 当前应该进行回调的方法对应的事件,这个可能为event的父类、接口,所以不一定和event是同一个class
     * @return true表示发送成功
     */
    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //通过当前观察者需要处理的事件获得观察元素
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            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;
                    //这里返还了发送任务的canceled标记,意味着这个标记一般的用途是在串行事件中
                    //目前只能通过cancelEventDelivery处理,而且要求必须是POSTING处理
                    //说明这个设计的原意就是有的时候在某一个事件中不希望后续事件继续进行处理,比方说高优先级阻断低优先级
                    //此时在同一个线程中设置postingState.canceled为true,然后aborted为true,之后的事件就不会继续分发了
                    postingState.canceled = false;
                }
                if (aborted) {//如果当前线程的发送任务被取消,终止后续事件的发送
                    break;
                }
            }
            return true;
        }
        return false;
    }

这里主要有三个关注点:
1.在进行事件回调的时候,通过正序遍历,从而结合之前的优先级顺序进行回调。
2.事件回调的时候可以通过eventInheritance使得那些观察当前事件父类或者实现的接口的观察者同样可以收到当前事件。
3.事件的发送在不同的线程有不同的发送者,这个发送者有着自己的发送事件队列和参数
4.当前观察者等数据都是记录在内存当中,回调的时候仅仅使用的是内存中的数据,所以说EventBus不支持跨进程的通知。

    //每一个线程都有自己对应的发送状态等参数
    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };

    final static class PostingThreadState {
        //当前线程对应的发送事件列表
        final List<Object> eventQueue = new ArrayList<Object>();
        //当前线程是否处于事件发送中
        boolean isPosting;
        //当前线程是否为主线程
        boolean isMainThread;
        //当前posting中的观察元素
        Subscription subscription;
        //当前posting的事件
        Object event;
        //当前线程发送操作是否被取消
        boolean canceled;
    }

观察者进行事件处理

当一个事件发送到总线上,然后总线通过发送器将事件发送到一些对应的观察者,然后观察者开始进行事件的回调

    /**
     * 根据观察者的回调方法中定义的线程模式来进行不同的事件回调
     * @param subscription 当前需要进行回调的观察元素
     * @param event 当前需要回调的方法的参数,也是当前需要处理的事件
     * @param isMainThread 当前是否处于主线程
     */
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING://直接在当前post的线程中执行
                invokeSubscriber(subscription, event);
                break;
            case MAIN://在ui线程中执行
                if (isMainThread) {//当前post在主线程中,那么直接执行
                    invokeSubscriber(subscription, event);
                } else {//否则将当前事件交给主线程Poster等待执行
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BACKGROUND://在子线程中执行
                if (isMainThread) {//当前post在主线程中,将事件交给指定Poster等待在线程池中的某一个线程执行
                    backgroundPoster.enqueue(subscription, event);
                } else {//当前post在子线程中,直接在当前线程执行
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC://异步执行
                //一定在子线程中执行,但是不管当前在什么线程,都会进入线程池中等待调度执行
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

    /**
     * 这个用于在Poster里面进行事件回调处理
     * 但是因为存在一些异步的Poster,所以说在进行回调之前还是要检查当前是否unregister
     */
    void invokeSubscriber(PendingPost pendingPost) {
        //记录当前需要进行回调处理的元素
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        //将当前节点进行回收
        PendingPost.releasePendingPost(pendingPost);
        //有的时候异步进行回调的时候
        //可能当前的观察者已经被注销了
        //所以这里要进行检查
        if (subscription.active) {
            invokeSubscriber(subscription, event);
        }
    }

    /**
     * 进行方法的调用
     * @param subscription 当前需要回调的观察元素
     * @param event 当前需要回调的事件,也是回调函数的参数
     */
    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);
        }
    }

    /**
     * 用于在观察者的事件回调方法中调用,那么当前事件所对应的后续的观察元素的回调都不会进行
     * 常用的场景就是高优先级阻断低优先级的执行
     * 调用这个方法必须要求当前的回调方法是POSTING线程,因为这样才能保证同步性
     * 这个和postSingleEventForEventType处理的逻辑有关
     */
    public void cancelEventDelivery(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        //实际上这个方法是用于在事件回调的时候调用的,所以说需要检查当前是否处于事件发送中
        if (!postingState.isPosting) {
            throw new EventBusException(
                    "This method may only be called from inside event handling methods on the posting thread");
        } else if (event == null) {
            throw new EventBusException("Event may not be null");
        } else if (postingState.event != event) {
            throw new EventBusException("Only the currently handled event may be aborted");
        } else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
            throw new EventBusException(" event handlers may only abort the incoming event");
        }

        postingState.canceled = true;
    }

1.不同的ThreadMode会决定不同的回调处理线程

    public enum ThreadMode {
    /**
     * 观察者的回调方法会在post的线程中进行回调。这个是默认值,可以避免线程的频繁切换
     * 相对适合一些简单的任务(可以知道在很短的时间内处理的)
     * 对于那些耗时的任务来说,如果当前post的线程为UI线程
     * 会导致posting这个过程的阻塞(UI线程不应该被阻塞),这样就不合适了
     */
    POSTING,

    /**
     * 观察者的回调方法在UI线程中进行回调。如果post在UI线程则直接调用,都这进入主线程Handler中处理。
     * 同样的,在这种模式下不应该进行耗时操作,从而避免阻塞UI线程
     */
    MAIN,

    /**
     * 观察者的回调方法在子线程中进行。如果当前post在子线程则直接调用。
     * 否则进入线程池中执行,在设计的时候是尽可能使用单一的线程
     * 也就是说在post连续多个事件的时候,要稍微留意一下阻塞的情况
     * 某一个事件回调导致子线程阻塞,这同样会阻塞后面的事件进行处理的时机
     */
    BACKGROUND,

    /**
     * 观察者的回调方法在子线程中进行。
     * 不同于BACKGROUND的是这里直接将回调在线程池中执行
     * 也就是调度的工作交给了线程池,默认使用的是cacheExecutor
     * 所以说响应速度还是很快的,但是也要注意不要导致CPU多余忙碌
     */
    ASYNC
}

2.可以通过cancelEventDelivery来取消事件的发送,不过这个只是用于在高优先级回调后阻断低优先级处理,所以要求发送模式为POSTING,只有在同步进行事件回调处理的时候,才能保证低优先级的一定在高优先级的回调之后执行,这样才能正确中断事件。
3.当事件没有观察者处理或者出现异常的时候,如果允许发送一些默认的事件,那么可以通过观察这些默认的事件来做一些统计、日志之类的操作。

观察者注销

    /**
     * 反注册制定的观察者,会将当前观察者和其观察的事件缓存移除
     * 并且将对应的观察元素移除
     * @param subscriber 当前不再需要观察事件的观察者
     */
    public synchronized void unregister(Object subscriber) {
        //实际上就是将当前观察者已经注册的所有缓存移除
        //包括自身和事件的缓存、自身单独拥有的事件移除等等
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

    /**
     * 通过制定的观察者和事件,进行反注册,就是移除缓存
     * @param subscriber 当前需要反注册的观察者
     * @param eventType 当前需要反注册的观察者曾经观察的事件
     * */
    private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

一般来说在Android中观察者是Activity,因为EventBus中通过强引用持有观察者,那么在Activity销毁的时候如果不进行注销,就会导致内存泄漏,这个要注意一下。
简单来说就是在观察者不需要继续观察事件的时候记得注销观察者。

事件回调及函数注册相关

观察者需要定义方法来处理指定的事件,EventBus通过注解Subscribe来识别,这样的最大好处就是可以自定义函数名,而不需要像旧版本一样必须是onEvent开头。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * 当前回调方法是否处理黏性事件
     */
    boolean sticky() default false;

    /**
     * 回调优先级,这个只有对于同样的线程才有效,优先级高的会先响应事件
     * */
    int priority() default 0;
}

使用的时候就是类似

@Subscribe(threadMode = ThreadMode.POSTING, priority = 10)
    public void handlerLogin(Activity activity){
        //做些什么。。。
    }

通过这个注解可以定义为EventBus中的回调函数,处理的是Activity这个事件。
接着看EventBus是如何查找观察者中定义的回调函数,这个在之前register中有提到,实际上是通过SubscriberMethodFinder处理

    /**
     * 通过观察者查找其所定义的事件回调方法
     * @param subscriberClass 观察者
     * @return 该观察者所定义的事件回调方法列表
     */
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //如果该观察者之前注册过,但是后来注销了,那么下一次重新注册的时候,直接从缓存中获取即可
        //不需要每一次都重新查找一遍,效率会好一些
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            //忽略索引的话默认使用反射,相对索引来说还是慢不少的
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //通过索引查找,正常来说索引会在编译器生成,然后会写一个新的文件,里面会通过一个map存储所有结果
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {//如果一个观察者内部没有满足的回调方法,直接抛出异常了
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {//进行缓存
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

    /**
     * 实际上索引在编译期中就已经生成
     * 存在一个static的map存储
     * 这里其实就是通过当前的观察者拿出这些函数而已
     */
    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

    /**
     * 通过索引从某一个观察者中获取所有可以处理事件的函数
     * @param findState 用于存储查找结果
     */
    private SubscriberInfo getSubscriberInfo(FindState findState) {
        //之前在匹配索引的时候,可能当前class是之前事件的父类,那么这个时候可能有缓存
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {//从索引中获取
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

    /**
     * 通过反射来找观察者中的用于回调事件的方法
     * 注意这个是会去找观察者的父类
     * @param subscriberClass 观察者
     */
    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

    /**
     * 通过反射指定的注解来从某一个观察者中获取所有可以处理事件的函数
     * @param findState 用于存储查找结果
     */
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // 相对于getMethods来说会快不少,特别当观察者是一些特别巨大的类
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            //出现异常的时候通过getMethods再次尝试,并且跳过父类
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();//获得函数的修饰
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();//获得函数的参数
                if (parameterTypes.length == 1) {//EventBus的观察者回调处理参数默认就是1个
                    //查找对应的注解Subscribe
                    //EventBus3.0通过反射只认注解
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];//获得当前事件
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            //将当前满足条件的函数存入findState中
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

可以看到EventBus支持两种查找方式,一种是反射注解,一种是通过索引。
反射运行时注解的效率是偏低的,所以说一般来说推荐使用索引,索引后面会提到是通过编译期间生成的,所以说在使用的时候单纯是通过从map中取出,这个的效率是非常可观的,当然对应的内存相对会占用多一点点。

索引

EventBus通过编译期间处理注解,从而预先将结果保存,后期直接获取即可,这个具体实现在EventBusAnnotationProcessor

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")//用于处理Subscribe这个注解
@SupportedOptions("eventBusIndex")
public class EventBusAnnotationProcessor extends AbstractProcessor {
    //...
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
            //...省略一些

            //这里是收集整个项目中的非static,而且为public的有Subscribe注解的函数
            //总之就是满足EventBus回调处理事件的函数
            collectSubscribers(annotations, env, messager);
            checkForSubscribersToSkip(messager, indexPackage);

            if (!methodsByClass.isEmpty()) {//当前项目中有满足EventBus回调条件的用Subscribe注解的函数
                createInfoIndexFile(index);//创建文件,主要是构建一个map,用于存储当前获得的所有观察者和函数
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }

            //...省略一些
    }

这里忽略其它实现细节,简单说就是在编译器间,会自动查找所有有Subscribe注解的类和方法,然后记录有效的观察者和函数等参数(称为索引)起来,并且写一个新的java文件,其中包括一个map,然后将之前记录的索引放入map中。

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
 
apply plugin: 'com.neenbedankt.android-apt'
 
dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
 
apt {
    arguments {
        eventBusIndex "com.example.myapp.MyEventBusIndex"
    }
}

通过设置指定的eventBusIndex可以生成对应的索引类,上面曾经提到写的java文件其实就是这个,那么在编译完成之后,只需要通过EventBusBuilder将当前类添加即可。

public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if(subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

如此一来索引就成功添加到Builder当中,这里支持添加多个索引,最后只需要通过该Builder创建EventBus即可。

总结

EventBus在不跨进程的场景下还是相当方便的,特别是作为通知。
如果使用EventBus的话,个人推荐使用索引的方式,毕竟说可以自动生成,当然需要手动添加这个有点麻烦,不过这样可以很有效的减去反射的开销,效率上面还是很棒的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,391评论 25 707
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,465评论 0 50
  • 作者:Markus JungingerGithub:greenrobot/EventBus原文:老司机教你 “飙”...
    敲代码的本愿阅读 3,748评论 3 11
  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 3,158评论 0 6
  • 《像你》 /南深 经历了黑暗的光 像老故事里的泛黄桥段,半聋半哑,失了声息 像雾绵延一万里,不知何处去 你在夜空中...
    南深小小阅读 330评论 0 1