Android 事件EventBus——— 设计原理

EventBus的出现完美的解决了多个线程和多个activity、fragment之间的通信问题,下面我们来讲解一下具体的实现原理:
本篇不涉及任何源码,单纯讲一下它的流程,然后讲一下优缺点。
Eventbus 中有三个集合,这基本就是核心所在。

第一个集合

    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//第一个集合是用来存储的
//key是数据类型的clazz 
//value 是一个Subscription集合,
//Subscription对象是一个包装类,包含注册的时候传进去的对象 和对象中一个带 @Subscribe 注解的方法
public class A {
    
    @Subscribe (threadMode = ThreadMode.MAIN)
    public void accept1(Person p){
        
    }
    @Subscribe (threadMode = ThreadMode.MAIN)
    public void accept2(Person p){
        
    }
}
public class B {
    
    @Subscribe (threadMode = ThreadMode.MAIN)
    public void accept3(Person p){
        
    }
    @Subscribe (threadMode = ThreadMode.MAIN)
    public void accept4(Person p){
        
    }
}
public class C {
    
    @Subscribe (threadMode = ThreadMode.MAIN)
    public void accept1(Student s){
        
    }
     @Subscribe (threadMode = ThreadMode.MAIN)
    public void accept2(Student s){
        
    }
   
}
 

1,当 A 类调用 EventBus.getDefault().register(this);进行注册之后 ,

subscriptionsByEventType集合长度为 1,第一个key是 Person.Class ,value 就是一个长度为2的CopyOnWriteArrayList对象,CopyOnWriteArrayList中

第一个Subscription 是A对象的实例和 A对象的 accept1方法封装成的 Subscription 对象,

第二个Subscription 是A对象的实例和 A对象的 accept2方法封装成的 Subscription 对象

2,当B类对象调用 EventBus.getDefault().register(this);进行注册之后 ,

subscriptionsByEventType集合长度为 1,第一个key是Person.Class ,value 就是一个长度为4的CopyOnWriteArrayList对象,CopyOnWriteArrayList中

第一个Subscription 是A对象的实例和 A对象的 accept1方法封装成的 Subscription 对象,

第二个Subscription 是A对象的实例和 A对象的 accept2方法封装成的 Subscription 对象,

第三个Subscription 是B对象的实例和 B对象的 accept3方法封装成的 Subscription 对象,

第四个Subscription 是B对象的实例和 B对象的 accept4方法封装成的 Subscription 对象,

3,当C类对象调用 EventBus.getDefault().register(this);进行注册之后 ,

subscriptionsByEventType集合长度为 2,

第一个key是 Person.Class ,第一个key对应的Value 就是一个长度为4的CopyOnWriteArrayList对象,

第一个CopyOnWriteArrayList对象中的四个对象《步骤2中的四个Subscription 》

第二个key是 Student.Class, 第二个key对应的value 是一个长度为 2的 CopyOnWriteArrayList 对象,

第二个CopyOnWriteArrayList对象中的两个个对象

第一个Subscription 是C对象的实例和 C对象的 accept1方法封装成的 Subscription 对象,

第一个Subscription 是C对象的实例和 C对象的 accept2方法封装成的 Subscription 对象,
总结一下 ,

  • 1,所有订阅的方法中,有多少参数类型,subscriptionsByEventType 的长度就是多少,每个参数类型 对应一个list集合。
  • 2,该参数类型有多少个订阅的方法 ,对应的list集合长度就是多少,每一个Subscription 对象都是订阅的方法和方法所在的类的 实例。

这个集合的作用就是用来 分发事件用的,当发事件的时候,根据分发的事件类型的Class ,拿到对应订阅的方法的集合CopyOnWriteArrayList对象,然后逐一遍历进行分发,这也就是分发的原理。当然真正的流程没有这么简单,还涉及到了线程切换等等,这里不细说,主要介绍主流程。

伪代码

 
  public void register (Object subscriber){
            Class<?> tagClass = tag.getClass();
            List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(tagClass);
//通过反射拿到所有方法,SubscriberMethod封装的方法的参数类型,线程类型,优先级,粘性
            synchronized (this) {
                //遍历所有方法
                for (SubscriberMethod subscriberMethod : subscriberMethods) {
                    //拿参数类型
                    Class<?> eventType = subscriberMethod.eventType;
                    //进行封装 Subscription 除了包含方法所有参数之外还有tag的引用
                    Subscription newSubscription = new Subscription(tag, subscriberMethod);
 
//根据参数类型去第一个集合中判断是否有当前集合,如果没有就创建新的集合,如果有就根据优先级把当前的订阅方法封装添加进去
                    CopyOnWriteArrayList<Subscription> list = subscriptionsByEventType.get(eventType);
                    if (list == null) {
                        list = new CopyOnWriteArrayList<>();
                        subscriptionsByEventType.put(eventType, subscriptions);
                    } else {
                        //这里根据优先级添加,我没有写。
                        list.add(Subscription);
                    }
                }
            }
        }

再说一下getDefult():getDefult()使用的单例双重锁创建了EventBus实例
在这里插入图片描述

第二个集合

 private final Map<Object, List<Class<?>>> typesBySubscriber;
//这个集合的作用主要解除注册用的 ,
//key是注册时候的tag
//vlaue 的值是一个参数类型Clazz集合。

当有类进行注册的时候,对第一个集合进行操作的同时,也会对第二个集合进行操作,(上边的流程)

1,当 A 类调用 EventBus.getDefault().register(this);进行注册之后 ,

typesBySubscriber集合长度为 1,第一个key的A对象 ,value 就是一个长度为2的List<Class<?>对象,

第一个Class是Person.Class,

第二个Class是Person.Class,

2,当B类对象调用 EventBus.getDefault().register(this);进行注册之后 ,

typesBySubscriber集合长度为 2,

第一个key的A对象 ,value 就是一个长度为2的List<Class<?>对象,

《步骤一中的list 》

第二个Key是B对象,Vlaue 就是一个长度为2的List<Class<?>对象,

第一个Class是Person.Class,

第二个Class是Person.Class,

3,当C类对象调用 EventBus.getDefault().register(this);进行注册之后 ,

typesBySubscriber集合长度为 3,

第一个key的A对象 ,value 就是一个长度为2的List<Class<?>对象,

《步骤一中的list 》

第二个Key是B对象,Vlaue 就是一个长度为2的List<Class<?>对象,

《步骤二中的list 》

第三个Key 是C对象,value 就是一个长度为2的List<Class<?>对象,

第一个Class是Student.Class,

第二个Class是Student.Class,

在总结一下 ,

1,有多少个对象订阅了,typesBySubscriber的长度就是多少,每个订阅对象对应一个list集合。

2,每个订阅的对象中有多少个订阅的方法,list的长度就是多少,源码就是遍历所有订阅的方法,list把每个方法的参数添加进去。

这个集合主要的作用是用来解除注册的,

解除注册的时候,首先拿到要解除注册对象的所有方法的参数class的集合,然后遍历这个class集合,遍历的时候 通过第一个集合拿到每一个class所有的订阅方法,这里拿到的也是一个集合CopyOnWriteArrayList ,在遍历这个集合,凡是Subscription中 持有的对象 和当前要解除的对象一样的情况下,就把当前Subscription对象 从CopyOnWriteArrayList当中移除。遍历完所有list 集合之后就成功的解除了当前对象所有的订阅,最后从typesBySubscriber 把 当前对象保留的集合移除

伪代码


        public synchronized void unregister(Object tag) {
// 拿到当前对象所有的订阅方法
            List<Class<?>> subscribedTypes = typesBySubscriber.get(tag);
            if (subscribedTypes != null) {
//遍历所有方法
                for (Class<?> eventType : subscribedTypes) {
//拿到每个参数类型的所有订阅
                    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);
//如果订阅的方法 中tag的和当前解除的一样就移除
                            if (subscription.subscriber == tag) {
                                subscriptions.remove(i);
                            }
                        }
                    }
                }
                typesBySubscriber.remove(subscriber);
            }
        }

第三个集合的作用 作为粘性分发的作用

    private final Map<Class<?>, Object> stickyEvents;
 
//第三个集合是粘性分发
//key是数据类型的clazz 
//value 是数据对象,
 

事件分发 分为两种:正常分发 和粘性分发

第一种,正常分发

直接上伪代码

 public void post(Object event) {
        Class<?> eventClass = event.getClass();
        //从第一个集合中,通过参数类型按到需要分发所有方法的集合
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventClass);
        //遍历方法进行分发
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
               //判断线程,切换线程的代码省略,简单说一下,四种类型 
                // 第一种 POSTING 不做线程切换
                // 第二种 MAIN 如果发送的不在主线程 通过handler 切换线程
                // 第三种 BACKGROUND 如果不是主线程的就不进行线程切换,如果是主线程就进行线程切换,通过线程池进行切换
                // 第四种  ASYNC ,无论在不在主线程,都进行线程切换,通过线程池切换
                //反射调用方法,把事件发送出
                subscription.subscriberMethod.method.invoke(subscription.tag, event);
            }
 
        }
    }

第二种粘性分发:先分发,后注册也能收到消息,原理就在此,正常分发,然后把数据存起来
1,粘性分发首先执行一遍上边正常的流程。

2,利用第三个集合 把分发的数据保存起来

再讲前边注册的流程中 最后一步,

伪代码

public void register(Object  tag){  
............
 
//第一个集合操作过程
//第二个集合操作过程
 
..........
//判断是粘性事件
   if (subscriberMethod.sticky) {
        //拿到需要分发的数据对象 ,然后单独对当前订阅的方法进行分发
        Object stickyEvent = stickyEvents.get(eventType);
        subscriberMethod.invoke(tag, stickyEvent);
    }
 
}

最后简单串联一下逻辑:

EventBus原理,主要是通过注解和反射实现,将方法保存在公共队列中供其调用,首先在register()的时候,通过反射把当前类的所有方法遍历,然后把带有@Subscribe注解的方法保存在队列中,在调用的时候发送post方法,与队列中的方法进行匹配,这里只匹配方法的参数,如果一样的话就掉起该方法。

注册的时候

  1. 把当前对象所有注册方法放到第一个集合当中管理,为了分发 用。

  2. 同事第二个集合保存的数据就是记录了那些对象注册了,用来解除注册。

  3. 如果是粘性事件 去第三个集合中寻找对应的数据类型和数据,然后直接进行分发。

发送事件的时候

根据发送的事件类型找到所有需要发送对象的对应方法

解除注册的时候,

通过第二个集合找到所有注册的方法,然后进行逐个判断进行解除注册。

最后说一下个人所认为的优缺点

优点

可以跨模块发送消息。原理就是动态注册动态发送

缺点 : 接受事件类型

  1. 事件类型会定义很多

  2. 不同模块的消息类型必须两个模块都能引用到才行

  3. 不是流程式的代码,阅读性差。个人倾向于流程式的代码。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值