美文网首页源码解析
EventBus源码阅读笔记

EventBus源码阅读笔记

作者: 都有米 | 来源:发表于2018-07-27 14:15 被阅读4次

    一、简介

    EventBus是一个事件“发布/订阅”总线,可用于Android或者Java项目中。它具有以下优点:

    1. 简化组件间的通信
      • 解耦事件发布方和订阅方;
      • 很好的实现在Activities、Fragments和后台线程间通信;
      • 避免复杂的易错的依赖和生命周期问题;
    2. 使代码更简洁;
    3. SDK体积很小(约50k);
    4. 已经在上亿的安装应用中使用;
    5. 具有高级特性,如跨线程事件分发、按优先级分发等。
    EventBus
    下文我们把Publisher翻译成事件发布器,把Subscriber翻译成事件订阅者,Event翻译成事件,EventBus翻译成总线

    二、EventBus使用三部曲

    1. 定义事件类型

    public class MessageEvent {
        public String msg;
    }
    

    2. 事件订阅

    public class MainActivity extends Activity {
        @Override
        protected void onResume() {
            super.onResume();
            // 将事件订阅者注册到总线中
            EventBus.getDefault().register(this);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            // 将事件订阅者从总线中注销掉
            EventBus.getDefault().unregister(this);
        }
    
        // 定义事件接收器处理事件的方法
        @Subscribe(threadMode = ThreadMode.MAIN)
        public void onMessageEvent(MessageEvent event) {
            Log.d(TAG, "onMessageEvent="+event.msg);
        }
    }
    

    3. 事件发布

    public void sendEventMsg(View v){
        // 事件发布器发布事件
        MessageEvent event = new MessageEvent();
        event.msg = "hello";
        EventBus.getDefault().post(event);
    }
    

    三、源码分析

    正如官方文档介绍,sdk真的很小。核心类EventBus.java就500行代码完成了所有功能。短小精悍、简洁易用、稳定高效,实为我辈开发之楷模。EvenBus核心任务就两个:1、事件订阅者的管理;2、事件的分发。先总的看下整体架构,类图大致如下:

    类图

    1. EventBus的创建

    EventBus SDK使用不需要任何初始化操作。在需要订阅或者发布事件时,通过EventBus.getDefault()方法获取一个全局的EventBus的单例来执行相关操作。下面是SDK源码中单例创建的代码,没什么好说的标准的单例创建。

    // double check lock
    static volatile EventBus defaultInstance;
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus(); 
                }
            }
        }
        return defaultInstance;
    }
    

    2. EventBus对事件订阅者的管理逻辑

    上面EventBus使用部分介绍了,事件接收器的注册/注销是通过EventBus实例的register/unregister方法来完成的。我们重点分析下register方法的实现,unregister方法类似就不重复分析了。

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    

    事件订阅者注册register(Object subscriber)方法接收一个Object对象作为入参。

    1. 通过findSubscriberMethods(Class<?> subscriberClass)方法分析出该对象的类型对象中所有事件处理方法。是通过反射的方式找到我们用@Subscribe注解的方法,如下代码所示。
    2. 将Object对象和上面找到的每个事件处理方法都分别组成一个事件订阅者Subscription,也就是说如果一个订阅对象类里面实现了多个事件处理方法,那就会生成多个事件订阅者。然后将这些事件订阅者对象放到集合Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType中保存起来,待事件分发时使用。subscriptionsByEventType是一个map,map的key是事件类型,value是相同事件的订阅者的列表。插入到列表的时候是按照订阅者优先级顺序插入的,后续事件分发的时候就会按照事件优先级来顺序分发。

    类比上面使用说明中的例子,我们传入的Object对象是一个Activity对象,事件处理方法就是onMessageEvent方法。在EventBus中是将这个Activity对象和onMessageEvent方法和成了一个事件订阅者Subscription,然后存在数剧集合中的。上面类图中事件订阅者Subscription的结构清楚的展示了这一点。

    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods = findState.clazz.getDeclaredMethods();
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            // 1、必须是public类型,且不能是被static、abstract修饰的方法
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 2、方法有且只有一个参数
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    //3、方法必须是被Subscribe注解的方法
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            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");
            }
        }
    }
    

    unregister的过程我们就不分析了,类似register的逆过程,就是从数据集合中移除对应的事件订阅者的过程。

    3. EventBus对事件的分发逻辑

    事件发布post(Object event)方法接收一个事件对象作为入参。还记得我们事件订阅者是存在以事件类型为key的map中,那事情就简单了,通过入参的事件类型获取所有订阅此事件的事件订阅者列表。

    1. 执行事件订阅者Subscription中事件订阅对象的事件处理方法;
    void invokeSubscriber(Subscription subscription, Object event) {
      subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    }
    
    1. 跨线程分发
      EventBus中有两个poster。mainThreadPoster:如类图所示它继承了Handler,关联的是主线程的Looper,所以可以通过mainThreadPoster将事件发送到主线程执行;backgroundPoster:如果所示本身是个Runnable,关联了一个线程池,所以它是将事件发送到线程池去执行的。
      还有一种类型是不适用上面的poster,直接就在事件发布器调用post(Object event)方法的线程执行事件处理方法,这就实现了在当前线程分发事件。

    四、学习

    1、Double-Check单例创建法
    • Double-Check是为了执行提高效率,只在首次获取实例的时候有线程同步的开销,后续获取实例时不涉及同步;
    • 不同步的引用对象不是线程安全的,除double和float外的基本变量类型是线程安全的。所以在double check模式下引用对象、double和float变量都需要使用volatile 关键字来保证其线程安全。
    2、注解

    RetentionPolicy.RUNTIME类型的注解可以在运行时通过反射的方式获取到注解的内容。如下运行时注解Subscribe可应用到方法上。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Subscribe {
        ThreadMode threadMode() default ThreadMode.POSTING;
        boolean sticky() default false;
        int priority() default 0;
    }
    

    运行时通过反射获取到注解的相关信息。

    Method[] methods = findState.clazz.getDeclaredMethods();
    
    for (Method method : methods) {
        Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
        if (subscribeAnnotation != null) {
            ThreadMode threadMode = subscribeAnnotation.threadMode();
        }
    }
    
    3、多线程编程

    根据前文的介绍可以了解整个框架中的临界资源应该是这个Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType数据集合对象了。在注册/注销时间订阅者和事件分发是都需要操作这个数据集合,可能出现多个线程同时注册/注销时间订阅者,或者注册/注销时间订阅者和事件分发不在同一个线程,这些场景都有多线程的问题。
    我们来看看作者是怎么处理的。

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    
    public synchronized void unregister(Object subscriber) {
        ... ...
    }
    
    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        ... ...
        return false;
    }
    

    subscriptionsByEventType的操作使用synchronized关键字来同步。另外对于事件订阅者列表使用CopyOnWriteArrayList来同步修改列表,同时让列表的遍历更加高效。

    相关文章

      网友评论

        本文标题:EventBus源码阅读笔记

        本文链接:https://www.haomeiwen.com/subject/frepmftx.html