教你自己实现一个事件总线EventBus

EventBus是一个Github上著名的开源事件总线框架,想必很多人都使用过它。它实现了事件订阅者和事件发布者的解耦,让我们更加容易在actvity等组件间传递信息。

我们虽然不喜欢重复造轮子,但是不代表我们不需要了解轮子是怎么造的。

这篇文章通过这个简单的实例,给大家说明EventBus实现的原理,一起来打造一个简单的事件总线框架。如果你明白了这个框架的设计原理,那么EventBus也就相差不大,两者比起来只是后者更加完善和高效。

这也给看源码的朋友带来便利,大家可以先尝试看我这篇文章,再去看EventBus的源码哦!

由于是模仿EventBus写得,所以我也把这个“框架”称为EventBus,先来看一下我们怎么使用这个框架。

现在我们实现的一个效果是,有两个acivity如图

MainActivity:当点击"click"按钮的时候,就会跳转到secondActivity,而secondActivity里面有两个按钮,点击以后,可以改变MainActivity里面TextView的文字

d

改变以后如图:

这其实就是一个消息传递的过程,假设我们用EventBus,下面我们来看一下,代码是怎么写的

public class MainActivity extends FragmentActivity {

    Button btn;
    TextView text;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getInstance().register(this);
        btn = (Button) findViewById(R.id.btn);
        text = (TextView) findViewById(R.id.text);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,SecondActivity.class));
            }
        });
    }

    public void onEvent(Info i){
        Log.i("cky", i.msg);
    }

    public void onEventMain(Info i){
        text.setText(i.msg);
    }

    public void onEventMain(Info2 i){
        text.setText(text.getText()+i.msg);
    }
}

可以看到,我们在onCreate()里面调用了EventBus.register(this),来注册事件

然后又两者函数,一个是onEvent(),这个方法里面的代码,会在一个子线程中执行

一个是onEventMain(),这个方法里面的代码,会在UI线程执行。在这里,我们改变了TextView中的文字。

使用过EventBus的朋友,现在看来使用方式是不是跟EventBus很想(我就是想实现EventBus的效果啊)。EventBus对事件在不同线程中处理,有四种方式,但是我们这里只写了简单的两者,主线程和非主线程,这样代码更加简单,而且原理是一样的,会写两个,就会写多个。

然后SecondActivity里面,大家一定知道是怎么回事,肯定是在点击里面调用了post()方法

public class SecondActivity extends Activity {

    Button btn2;
    Button btn3;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        btn2 = (Button) findViewById(R.id.btn2);
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getInstance().post(new Info("信息1"));
            }
        });
        btn3 = (Button) findViewById(R.id.btn3);
        btn3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getInstance().post(new Info2("信息2"));
            }
        });
    }
}

就是这么简单,使用方法跟EventBus如出一辙。

大家可能会好奇到底怎么样用简单的代码,实现这样的效果,下面我带大家看我的代码。!

先看EventBus

public class EventBus {
    HashMap<Class<?>,ArrayList<Subscription>> subscriptionsByEventType =
            new HashMap<Class<?>,ArrayList<Subscription>>();
    MainThreadHandler mainThreadHandler = new MainThreadHandler(this,Looper.getMainLooper());
    AsyThreadHandler asyThreadHandler = new AsyThreadHandler(this);

    private final static EventBus instance = new EventBus();
    public static EventBus getInstance(){
           return instance;
    }
    private EventBus(){};
}

有几个地方:

1,将构造函数设为私有,使用了getInstance()来给外界提供EventBus实例,其实就是一个单例模式

2,有几个复杂的属性,首先是subscriptionsByEventType,这是一个map,key代表某个bean类。

使用过EventBus都知道,我们在传递信息的时候,要传递一个实体类,例如我上面的例子,就是Info这个类。

value值是一个ArrayList<Subscription>,Subscription的意义就是一个订阅,是一个我创建的实体类。这个类的具体含义留到后面讲。

剩下两个mainThreadHandler和asyThreadHandler,顾名思义就是分别用来处理主线程和子线程的,都是我创建的类。

看到这里可能大家有点糊涂,出现了三个类,别急,我一定会解释清楚。现在先让我们来看非常重要的register()方法。

public void register(Object subscriber){
        Class<?> clazz = subscriber.getClass();//获取订阅者的类型
        Method[] methods = clazz.getMethods();//获取该类的所有方法
        for(Method m:methods){//遍历方法
            String name = m.getName();//获取方法名
            if(name.startsWith("onEvent")){<span style="font-family: Arial, Helvetica, sans-serif;">//判断方法名是否以"onEvent"开头             </span>
                Class<?>[] params = m.getParameterTypes();//获取该方法的参数类型
                ArrayList<Subscription> arr;
                if(subscriptionsByEventType.containsKey(params[0])){
                    arr = subscriptionsByEventType.get(params[0]);
                }else{
                    arr = new ArrayList<Subscription>();
                }
                int len = name.substring("onEvent".length()).length();//截取方法除"onEvent"部分
                Subscription sub;
                if(len==0){//如果剩余长度为0,说明是子线程中执行
                    sub =  new Subscription(subscriber,new SubscriberMethod(m,params[0],0));
                }else{//否则,在主线程执行
                    sub =  new Subscription(subscriber,new SubscriberMethod(m,params[0],1));
                }
                arr.add(sub);
                subscriptionsByEventType.put(params[0],arr);
            }
        }
    }

从上面的注释我们可以看到,我们先获取了订阅者(例子中是MainActivity)的方法,找到onEvent开头的方法,获得它们的参数类型

然后判断subscriptionsByEventType是否有以这些参数类型为key的数据,如果没有,新建一个ArrayList<Subscription>。

然后我们先看Subscription

public class Subscription {
    Object subscriber;
    SubscriberMethod SubscriberMethod;
    public Subscription(Object subscriber, SubscriberMethod SubscriberMethod) {
        this.subscriber = subscriber;
        this.SubscriberMethod = SubscriberMethod;
    }
}

它代表一个订阅,拥有subsriber,也就是订阅者

还有一个SubscriberMethod,这是订阅方法类

public class SubscriberMethod {
    Method m;
    int type;
    public SubscriberMethod(Method m, Class<?> param, int type) {
        this.m = m;
        this.type = type;
    }
}

它有一个Method m属性,就是我们注册的方法,另外一个type就是标记,0代表m在子线程中执行,其余代表在主线程中执行。

所以总的来说register()通过遍历订阅者,找到订阅方法(onEvent(),onEventMain()),将方法包装成SubscriberMethod类,再讲SubscriberMethod和订阅者一起,

包装成Subscription类。

而subscriptionsByEventType根据参数类型为key,所谓参数类型,在例子中就是Info.class和info2.class

为什么我们要用参数类型为键呢?我们来想,以后我们在post的时候,是这样调用post的:

EventBus.getINstance().post(new Info("信息"));

我们要传进去一个参数,而EventBus的任务就是,调用所有注册了这个参数类型的订阅方法。

所有显然,我们要根据参数类型,找到这些方法,这是key是参数类型的原因了。

Ok,register()方法下来,最重要的一件事就是我们得到了subscriptionsByEventType

接下来我们就可以看post方法了

public void post(Object event){
        Class<?> clazz = event.getClass();
        ArrayList<Subscription> arr = subscriptionsByEventType.get(clazz);
        for(Subscription sub:arr){//遍历订阅
            if(sub.SubscriberMethod.type==0){
                asyThreadHandler.post(sub,event);
            }else{
                mainThreadHandler.post(sub,event);
            }
        }
    }

post()方法里面,如同我们上面所说,获取了参数类型,然后在subscriptionsByEventType中查询所有改类型对应的订阅Subscription

对于Subscription,它有我们订阅类的所有信息。

首先根据type判断是在主线程还是子线程执行,然后调用一开始讲到的两个类的实例就好了。

我们先看asyThreadHandler,这个会在子线程执行

public class AsyThreadHandler {
    private EventBus eventBus;
    AsyThreadHandler(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    public void post(final Subscription sub, final Object event){
        new Thread(){
            @Override
            public void run() {
                eventBus.invoke(sub,event,sub.SubscriberMethod.m);
            }
        }.start();
    }
}

代码很简单,其他它的post方法,就是启用了一个线程,然后在线程里面,调用了EventBus的invoke()方法。

我们看这个方法

public void invoke(final Subscription sub, final Object event,Method m){
        try {
            m.invoke(sub.subscriber,event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

其实只有一句话,就是调用了反射去执行方法。m是订阅方法,sub.subscriber就是订阅者,event就是post()方法传入的实体

这样我们就在子线程中调用了这个方法了,相当于MainActivity主动调用这个方法。

那么在主线程中呢,大同小异

public class MainThreadHandler extends Handler{
    private final EventBus eventBus;

    MainThreadHandler(EventBus eventBus, Looper looper) {
        super(looper);
        this.eventBus = eventBus;
    }

    @Override
    public void handleMessage(Message msg) {
        EventBus.getInstance().invoke(sub,event,sub.SubscriberMethod.m);
    }

    Subscription sub;
    Object event;
    public void post(Subscription sub, Object event){
        this.sub = sub;
        this.event = event;
        sendEmptyMessage(0);
    }
}

可以看到继承了handler,表明在主线程中调用。

post方法传递了一个空message,在handlerMessage()方法里面,又是调用了EventBus的invoke()方法。

殊途同归,最后都是调用EventBus的invoke()方法,不过一个在子线程中调用,一个在主线程中调用。

就这样,我们轻而易举得实现了EventBus的基本功能哦!

是不是觉得代码很简单,其实上面的代码,已经将EventBus框架的思路将清楚了,大家明白这思路,再看EventBus就容易多了。

通过学习,我们也注意到,EventBus(我是这github上面的)存在缺点

1,就是订阅方法名固定,就像我上面,只能用onEvent(),onEventMain()方法,使用者不能自己规定方法名

2,根据参数类型来调用方法,也就是post(Info i),所有的订阅了Info类的订阅方法都会被调用。很多时候我们不希望这样,而是希望post给特定的订阅方法。

OK,代码就讲到这里,不明白的地方可以留言问我,或者直接看源码。

源码地址下载:http://download.csdn.net/detail/kangaroo835127729/8992551

转载请注明出处!http://blog.csdn.net/crazy__chen/article/details/47425779

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-15 02:16:16

教你自己实现一个事件总线EventBus的相关文章

自己动手写事件总线(EventBus)

本文由云+社区发表 事件总线核心逻辑的实现. EventBus的作用 Android中存在各种通信场景,如Activity之间的跳转,Activity与Fragment以及其他组件之间的交互,以及在某个耗时操作(如请求网络)之后的callback回调等,互相之之间往往需要持有对方的引用,每个场景的写法也有差异,导致耦合性较高且不便维护.以Activity和Fragment的通信为例,官方做法是实现一个接口,然后持有对方的引用,再强行转成接口类型,导致耦合度偏高.再以Activity的返回为例,一

Guava: 事件总线EventBus

EventBus 直译过来就是事件总线,它使用发布订阅模式支持组件之间的通信,不需要显式地注册回调,比观察者模式更灵活,可用于替换Java中传统的事件监听模式,EventBus的作用就是解耦,它不是通用的发布订阅系统,也不能用于进程间通信.可用于Android的EventBus库主要有这几个:Google出品的Guava,Guava是一个庞大的库,EventBus 只是它附带的一个小功能,因此实际项目中使用并不多.用的最多的是greenrobot/EventBus,这个库的优点是接口简洁,集成方

android事件总线(eventbus)设计与实现

1. 功能介绍 AndroidEventBus是一个Android平台的事件总线库, 它简化了Activity.Fragment.Service等组件或者对象之间的交互,很大程度上降低了它们之间的耦合,使得我们的代码更加简洁,耦合性更低,提升我们的代码质量. AndroidEventBus吸收了greenrobot的EventBus以及square的otto的优点,并在此基础上做出了相应的改进,使得事件总线框架更适合用户的使用习惯,也使得事件的投递更加的精准.灵活. 与EventBus.otto

android事件总线(eventbus)开源库发布

 AndroidEventBus 如果你不知道事件总线是什么,那么没有关系,下面我们先来看这么一个场景: 你是否在开发的过程中遇到过想在Activity-B中回调Activity-A中的某个函数,但Activity又不能手动创建对象来设置一个Listener什么的? 你是否想在某个Service中想更新Activity或者Fragment中的界面? 等等之类的组件之间的交互问题-- 一经思考,你会发现Android中的Activity, Fragment, Service之间的交互是比较麻烦的,

事件总线 EventBus

661. .net中事件模型很优雅的实现了观察者模式,同时被大量的使用在各种框架中. [2016-04-30 10:52:42]662. Prism框架中实现了一个典型的EventAggregator,有时候我们又把此类实现叫做EventBus. [2016-04-30 10:53:12]663. EventBus内部通过一个类型为ConcurrentDictionary<Type,List<Action<object>>> 的字典来存储主题和观察者列表 [2016-0

事件总线EventBus使用详解

EventBus源码解析 概述 EventBus是针一款对Android的发布/订阅事件总线.它可以让我们很轻松的实现在Android各个组件之间传递消息,并且代码的可读性更好,耦合度更低. 如何使用 (1)首先需要定义一个消息类,该类可以不继承任何基类也不需要实现任何接口.如: 123 public class MessageEvent { ......} (2)在需要订阅事件的地方注册事件 1 EventBus.getDefault().register(this); (3)产生事件,即发送

ASP.NET Core基于微软微服务eShopOnContainer事件总线EventBus的实现

这个EventBus的实现是基于微软微服务https://github.com/dotnet-architecture/eShopOnContainers项目的,我把它从项目中抽离出来,打包成nuget包方便大家快速集成到项目中 从Nuget.org中安装 PM> Install-Package Toosame.EventBus.RabbitMQ -Version 1.1.2 使用 共3步: 添加事件 添加事件处理器 从控制器发布事件 1.添加事件 创建YourEvent.cs文件 1 publ

Android 开源框架 ( 七 ) 事件总线---EventBus

一.引言          与四大组件之一的BroadCast广播比较,广播主要监听系统级事件,比如网络切换,电池电量等属于进程间的通信,EventBus 是进程内的通信.         了解BroadCast 可以查看该文章:Android 四大组件 (三) BroadcastReceiver 介绍 二.基本使用 引入类库: compile 'com.jakewharton:butterknife:8.5.1' 页面打开时候初始化并注册EventBus //初始化 @Override pro

【java】简单的事件总线EventBus

public class EventBus { private static Map<String, EventListener> eventListeners = new HashMap<>(); public static void addEventListener(EventListener listener) { String listenerName = getListenerName(listener); eventListeners.put(listenerName,