EventBus探索和实现

今天这里实现的是一个简化版本的EventBus,实现组件和控件之间的通信,不涉及线程切换。

首先了解下什么是EventBus:

EventBus是一种用于Android的发布/订阅事件总线。它有很多优点:简化应用组件间的通信;解耦事件的发送者和接收者;避免复杂和容易出错的依赖和生命周期的问题;很快,专门为高性能优化过等等。

EventBus使用了发布者/订阅者模式,的基本原理可以用一张图表示:

D9D1414D5CB8A1B89A1C841C767565B4.jpg

事件的发布者通过post将事件发送到eventbus事件缓存池中,然后事件缓存池找到相应注册的事件,通过函数的invoke将事件发送到每一个订阅者。

EventBus实现的三要素:
1, 事件
2,事件订阅者
3,事件发布者

事件:其实就是一个消息,其实就是一个model对象,用于存储我们想要存储的内容,事件氛围一般事件和sticky事件,相对于一般事件,sticky事件不同之处在于,当事件发布后,再有订阅者订阅该类型事件,依然能收到该类型事件最近一个sticky事件。

订阅者:订阅某个事件类型的对象。当某个事件发布后,Eventbus会执行订阅该事件的onEvent函数,这个函数叫做事件响应函数。例如下面,Subscribe是订阅事件的关键字:

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Log.i(TAG, "message is " + event.getMessage());
        // 更新界面
        mTvMessage.setText(event.getMessage());
    } 

发布者:发布某个事件的对象,通过post接口发布事件。

EventBus.getDefault().post(messageEvent);

EventBus的四种线程模型:

POSTING (默认): 表示事件处理函数的线程跟发布事件的线程在同一个线程。
MAIN :表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
BACKGROUND :表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
ASYNC :表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

注意点:

(1)如果某个类里面的订阅者想要接收发布的事件,需要在onCreate中注册事件。

 //这句话内部实现了查找该类中所有订阅者方法,并且添加都订阅者事件执行表中。
EventBus.getDefault().register(this);     

(2)某个类注册了事件,在onDestroy中必须要销毁,不然会引起内存泄漏。

//这句话内部实现的就是在订阅者事件执行表中查找该类所有的订阅事件,并且移除这些事件。
EventBus.getDefault().unregister(this);

我在网上找到了两张订阅事件和发布事件的流程图,如下:

3DEC439453715E508DB4216F6A3F1788.jpg
3B7ABD017D12BD108764426A2C019A6F.jpg

大概就是这个样子了,用法很简单,但EventBus考虑的比较全面,今天我这边只想根据它的原理实现一个精简版的事件发送和订阅功能。

首先,定义一个注解类,用于注册消息订阅者:

/**
 * Created by dcw on 2018/12/24.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    String[] value();    //这里value里面接受的是订阅者的识别标签
}

第二步:定义一个订阅者方法保存类,这个类的作用就是用于保存所有的订阅事件的响应函数,这里保存的三个变量,一个是识别标签,一个是方法,一个是方法的参数类型。

/**
 * Created by dcw on 2018/12/24.
 */
public class SubscribeMethod {

    private String label;            //标签
    private Method method;           //方法
    private Class[] parmterClass;    //类型的参数

    public SubscribeMethod(String label, Method method, Class[] parmterClass) {
        this.label = label;
        this.method = method;
        this.parmterClass = parmterClass;
    }

    public String getLabel() {
        return label;
    }

    public Class[] getParmterClass() {
        return parmterClass;
    }

    public Method getMethod() {
        return method;
    }
}

第三步:定义一个Subscription类,这个类用于保存SubscribeMethod和SubscribeMethod所归属的类名称,方便后期查找。

/**
 * Created by dcw on 2018/12/24.
 */
public class Subscription {

    SubscribeMethod subscribeMethod;
    Object subscribe;

    public Subscription(SubscribeMethod subscribeMethod, Object subscribe) {
        this.subscribeMethod = subscribeMethod;
        this.subscribe = subscribe;
    }

    public Object getSubscribe() {
        return subscribe;
    }

    public SubscribeMethod getSubscribeMethod() {
        return subscribeMethod;
    }
}

最后写我们的DDBus主要类,这个类我注释得很详细,这里就不多解释了:

package com.ivalleytech.ddbus;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
* Created by dcw on 2018/12/24.
*/
public class DDBus {

   private static DDBus instance;
   private Map<Class,List<SubscribeMethod>> METHOD_CACHE = new HashMap<>();    //以类名归类,用于缓存每个类中所有注册的响应方法,一个标签一个方法
   private Map<String,List<Subscription>> SUBSCRIBES = new HashMap<>();        //以标签归类,存储一个标签下所有方法
   private Map<Class,List<String>> REGISTER = new HashMap<>();                 //存储这个类下所有的标签,用于协助注销类下的所有方法

   public static DDBus getInstance(){
       if (null == instance){
           synchronized (DDBus.class){
               if (null == instance){
                   instance = new DDBus();
               }
           }
       }
       return instance;
   }


   //注册订阅者
   public void registe(Object object){
       Class<?> subcribeClass = object.getClass();
       //找到对于类中所有被Subscribe标注的函数
       //将其Method,Label,执行函数需要的参数类型数组缓存
       List<SubscribeMethod> subscribes = findSubscribe(subcribeClass);

       //为了方便注销
       List<String> labels = REGISTER.get(subcribeClass);
       if (null == labels){
           labels = new ArrayList<>();
           REGISTER.put(subcribeClass,labels);
       }

       //执行表制作
       for (SubscribeMethod subscribeMethod:subscribes){
           //拿到标签
           String label = subscribeMethod.getLabel();
           if (!labels.contains(label)){
               labels.add(label);
           }

           //执行表数据是否存在
           List<Subscription> subscriptions = SUBSCRIBES.get(label);
           if (null == subscriptions){
               subscriptions = new ArrayList<>();
               SUBSCRIBES.put(label,subscriptions);
           }
           subscriptions.add(new Subscription(subscribeMethod,object));
       }
   }

   private List<SubscribeMethod> findSubscribe(Class<?> subcribeClass) {
       //先检测缓存中是否有这个注册
       List<SubscribeMethod> subscribeMethods = METHOD_CACHE.get(subcribeClass);
       //如果缓存中没有
       if (null == subscribeMethods){
           subscribeMethods = new ArrayList<>();
           //获得所有methods
           Method[] methods = subcribeClass.getDeclaredMethods();
           for (Method method: methods){
               Subscribe subscribe = method.getAnnotation(Subscribe.class);
               if (null!=subscribe){
                   String[] values = subscribe.value();
                   //获得这个函数的参数类型
                   Class<?>[] parameterTypes = method.getParameterTypes();
                   for (String value:values) {
                       //设置权限,方法可访问
                       method.setAccessible(true);
                       subscribeMethods.add(new SubscribeMethod(value,method,parameterTypes));
                   }
               }
           }
           METHOD_CACHE.put(subcribeClass,subscribeMethods);
       }
       return subscribeMethods;
   }

   /**
    * 发送事件给订阅者
    * @param lable
    */
   public void post(String lable, Object... params) {
       // params
       List<Subscription> subscriptions = SUBSCRIBES.get(lable);
       if (null == subscriptions) {
           return;
       }

       for (Subscription subscription : subscriptions) {
           SubscribeMethod subscribeMethod = subscription.getSubscribeMethod();
           //执行函数需要的参数类型数组
           Class[] paramterClass = subscribeMethod.getParmterClass();
           //真实的参数
           Object[] realParams = new Object[paramterClass.length];
           if (null != params) {
               for (int i = 0; i < paramterClass.length; i++) {
                   //传进来的参数 类型是method需要的类型
                   if (i < params.length && paramterClass[i].isInstance(params[i])) {
                       realParams[i] = params[i];
                   } else {
                       realParams[i] = null;
                   }
               }
           }
           try {
               subscribeMethod.getMethod().invoke(subscription.getSubscribe(), realParams);
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           } catch (InvocationTargetException e) {
               e.printStackTrace();
           }
       }
   }

   //清空所有
   public void clear(){
       METHOD_CACHE.clear();
       SUBSCRIBES.clear();
       REGISTER.clear();
   }

   //反注册销毁
   public void unregister(Object object) {
       //对应对象类型的所有 注册的标签
       List<String> labels = REGISTER.get(object.getClass());
       if (null != labels){
           for (String label : labels) {
               //获得执行表中对应label的所有函数
               List<Subscription> subscriptions = SUBSCRIBES.get(label);
               if (null != subscriptions){
                   Iterator<Subscription> iterator = subscriptions.iterator();
                   while (iterator.hasNext()){
                       Subscription subscription = iterator.next();
                       //对象是同一个才删除
                       if (subscription.getSubscribe() == object){
                           iterator.remove();
                       }
                   }
               }
           }
       }
   }
}

上面涉及到三个数据结构,分别是缓存表(METHOD_CACHE),执行表(SUBSCRIBES),注销表(REGISTER),他们之间的结构如下:
03875C01FD80F54DEDCD7489D430B944.jpg

MainActivity中执行如下:


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DDBus.getInstance().registe(this);

        //发送事件给带1标签的订阅者
        DDBus.getInstance().post("1","123","213");
    }

    //订阅1,2,3标签的事件
    @Subscribe({"1","2","3"})
    private void test(String msg,String msg1){
        Log.e("==","===MSG:"+msg+"===msg1:"+msg1);
    }

    @Subscribe({"1","2"})
    private void test1(String msg,Integer msg1){
        Log.e("==","MSG:"+msg+"===msg1:"+msg1);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //注销事件
        DDBus.getInstance().unregister(this);
    }
}

大概就是这样,一个简单的EventBus实现,这里也没有实现线程切换,如果想要实现可以在Subscription加一个标注,然后在

subscribeMethod.getMethod().invoke(subscription.getSubscribe(), realParams);

中取出subscription标注判断进行切换即可,原理就是这样,所有代码都贴在这里了,如果有疑问欢迎来讨论。

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

推荐阅读更多精彩内容