前言
EventBus相信大多数人都用过,其具有方便灵活、解耦性强、体积小、简单易用等优点,虽然现在也有很多优秀的替代方案如RxBus、LiveDataBus等,但不可否认EventBus开创了消息总线时代,有很多优秀的思想可以供我们来借鉴学习。下面就让我们来撸一个超简单的EventBus,领略其实现方式的主要思想。

实现思路
先看一下GitHub中官方用法介绍:
1.定义消息实体类

public static class MessageEvent { /* Additional fields if needed */ }
1
2.准备订阅者:声明并注解订阅方法

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
1
2
注册和注销订阅者

@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}

@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
1
2
3
4
5
6
7
8
9
10
11
3.发送事件

EventBus.getDefault().post(new MessageEvent());
1
EventBus通过register方法,查找注册对象中有Subscribe注解的方法,并把这些方法维护在EventBus自己的Map中,在某处调用post()方法发送消息时,通过消息载体Bean类,在Map中查找订阅了接收该Bean类的消息接收方法,调用该方法,达到消息传递的目的。

功能实现
准备工作
1.定义线程枚举类

public enum ThreadMode {
MAIN,
BACKGROUND
}
1
2
3
4
2.定义注解,注解目标是函数,运行时注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.MAIN;
}
1
2
3
4
5
3.定义方法封装类,用来描述使用了subscribe注解的消息接收方法,将维护在EventBus类中。

public class SubscribeMethod {

/**
* 方法本身
*/
private Method method;

/**
* 线程模式
*/
private ThreadMode threadMode;

/**
* 参数类型
*/
private Class<?> type;

public SubscribeMethod(Method method, ThreadMode threadMode, Class<?> type) {
this.method = method;
this.threadMode = threadMode;
this.type = type;
}

public Method getMethod() {
return method;
}

public void setMethod(Method method) {
this.method = method;
}

public ThreadMode getThreadMode() {
return threadMode;
}

public void setThreadMode(ThreadMode threadMode) {
this.threadMode = threadMode;
}

public Class<?> getType() {
return type;
}

public void setType(Class<?> type) {
this.type = type;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
定义EventBus类
单例模式,定义一个Map维护订阅的方法
private static volatile EventBus instance;
private Map<Object, List<SubscribeMethod>> cacheMap;

private EventBus() {
cacheMap = new HashMap<>();
}

public static EventBus getDefault() {
if (instance == null) {
synchronized (EventBus.class) {
if (instance == null) {
instance = new EventBus();
}
}
}
return instance;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
定义注册方法,在该方法中,查找订阅的方法,维护在map中,该map的key是注册消息的对象,如Activity、Fragment等

public void register(Object obj) {
//找到消息监听方法,如onMessageEvent,获取信息,统一管理
List<SubscribeMethod> list = cacheMap.get(obj);
if (list == null) {
list = findSubscribeMethods(obj);
cacheMap.put(obj, list);
}
}
1
2
3
4
5
6
7
8
其中findSubscribeMethods(obj)方法是用来查找对象中订阅方法,下面是其实现

/**
* 查找定义的消息接收方法
*
* @param obj 查找对象
* @return
*/
private List<SubscribeMethod> findSubscribeMethods(Object obj) {
List<SubscribeMethod> list = new ArrayList<>();
Class<?> clazz = obj.getClass();
//寻找自身及父类subscribe注解的方法
while (clazz != null) {
//如果是系统的类,过滤掉
String name = clazz.getName();
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
break;
}
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
//找到带有subscribe注解的方法
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe == null) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes();
//这里仅考虑只有一个参数的情况
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("消息监听方法有且只能有一个参数!");
}
ThreadMode threadMode = subscribe.threadMode();
SubscribeMethod subscribeMethod = new SubscribeMethod(method, threadMode, parameterTypes[0]);
list.add(subscribeMethod);
}
clazz = clazz.getSuperclass();
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
定义消息发送方法

/**
* 发送消息
*
* @param type 消息实体
*/
public void post(Object type) {
//循环cacheMap,找到对应的方法进行回调
Set<Object> set = cacheMap.keySet();
Iterator<Object> iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
List<SubscribeMethod> list = cacheMap.get(obj);
for (SubscribeMethod subscribeMethod : list) {
//匹配发送的消息bean和接收的消息bean是否相同
if (subscribeMethod.getType().isAssignableFrom(type.getClass())) {
invoke(subscribeMethod, obj, type);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
其中invoke(subscribeMethod, obj, type)反射调用回调的方法,实现如下

/**
* 反射调用回调方法
*
* @param subscribeMethod 方法对象
* @param obj 被反射对象
* @param type 方法参数
*/
private void invoke(SubscribeMethod subscribeMethod, Object obj, Object type) {
Method method = subscribeMethod.getMethod();
try {
method.invoke(obj, type);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OK!这么一个最简单的EventBus就写完了,现在让我们来测试一下吧

测试效果
我们打算在MainActivity中订阅,在SecondActivity中发送消息来测试
首先定义一个消息实体类

public class MessageBean {

private String param1;

private String param2;

public MessageBean(String param1, String param2) {
this.param1 = param1;
this.param2 = param2;
}

public String getParam1() {
return param1;
}

public void setParam1(String param1) {
this.param1 = param1;
}

public String getParam2() {
return param2;
}

public void setParam2(String param2) {
this.param2 = param2;
}

@Override
public String toString() {
return "MessageBean{" +
"param1='" + param1 + '\'' +
", param2='" + param2 + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
在MainActivity中注册订阅

public class MainActivity extends AppCompatActivity {

private static final String TAG = "Main";

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

EventBus.getDefault().register(this);

startActivity(new Intent(this, SecondActivity.class));
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageBean event) {
if (event != null) {
Log.e(TAG, event.toString());
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
收到消息后会打印出消息内容
最后在SecondActivity中发送消息

public class SecondActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

findViewById(R.id.tv_post).setOnClickListener(new View.OnClickListener(http://www.amjmh.com/v/) {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new MessageBean("SecondActivity","消息内容"));
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行后,点击SecondActivity中的发送按钮后,打印log如下:

com.cwq.eventbusdemo E/Main: MessageBean{param1='SecondActivity', param2='消息内容'}

教你快速写一个EventBus框架的更多相关文章

  1. 快速写一个babel插件

    es6/7/8的出现,给我们带来了很多方便,但是浏览器并不怎么支持,目前chrome应该是支持率最高的,所以为了兼容我们只能把代码转成es5,这应该算是我们最初使用babel的一个缘由,随着业务的开发 ...

  2. 教你快速写出多线程Junit单元测试用例 - GroboUtils

    摘自: http://mushiqianmeng.blog.51cto.com/3970029/897786/ 本文出自One Coder博客,转载请务必注明出处: http://www.coderl ...

  3. 用express快速写一个hello world

    首页要具备 node.js环境, npm环境 创建一个目录, 然后进入此目录作为工作目录 mkdir myapp cd myapp 通过npm init 创建一个package.json文件 npm ...

  4. 用Python手把手教你搭建一个web框架-flask微框架!

    在之前的文章当中,小编已经教过大家怎么搭建一个Django框架,今天我们来探索另外的一种框架的搭建,这个框架就是web框架-flask微框架啦!首先我们带着以下的几个问题来阅读本文: 1.flask是 ...

  5. 老司机教你用原生JDK 撸一个 MVC 框架!!!

    其实 Spring MVC 是一个基于请求驱动的 Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器进行处理,具体工作原理见下图. 在这里,就不详细谈相关的原 ...

  6. Android消息传递之基于RxJava实现一个EventBus - RxBus

    前言: 上篇文章学习了Android事件总线管理开源框架EventBus,EventBus的出现大大降低了开发成本以及开发难度,今天我们就利用目前大红大紫的RxJava来实现一下类似EventBus事 ...

  7. 第一个web框架tornado

    简介 tornado,是我学到的第一个web框架是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google ...

  8. 一个iOS 框架介绍:MKNetworkKit

    http://blog.csdn.net/kmyhy/article/details/12276287 http://blog.csdn.net/mobailwang/article/details/ ...

  9. 看过《大湿教我写.net通用权限框架(1)之菜单导航篇》之后发生的事(续)——主界面

    引言 在UML系列学习中的小插曲:看过<大湿教我写.net通用权限框架(1)之菜单导航篇>之后发生的事 在上篇中只拿登录界面练练手,不把主界面抠出来,实在难受,严重的强迫症啊.之前一直在总 ...

随机推荐

  1. paramiko模块实现远程传输控制

    一.什么是paramiko呢? paramiko是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,值得一说的是,fabric和ansible内部的远程管理就是使用的parami ...

  2. NLog Helpper日志帮助类配置和使用

    1.帮助类  (首先需要引入NLog.dll) using System; namespace XXXXXX { /// <summary> /// 用法实例 : NLogTest.Nlo ...

  3. js面向对象的几种方式

    对象的字面量 var obj={}:创建实例对象 var obj=new Object();构造函数模式 function fn(){}, new fn();工厂模式:用一个函数,通过传递参数返回对象 ...

  4. eclipse debug 调试找不到资源问题解决

    eclipse debug 的时候,如果使用maven bulid,就可能找不到class,这种情况就需要先停止服务,然后配置 Run configurations-Source,然后remove掉D ...

  5. String,StringBuffer,StringBuilder 的区别是什么

    Java中用于处理字符串常用的有三个类: 1.java.lang.String 2.java.lang.StringBuffer 3.java.lang.StrungBuilder 一.Java St ...

  6. Centos7虚拟机根分区扩展

    线上的kvm虚拟机,原来只规划了8G,后来发现硬盘动不动就被日志塞满了,需要进行扩容. 扩容步骤如下: 1.先把kvm虚拟机关机 2.在宿主机上进行kvm虚拟机的磁盘扩容 qemu-img resiz ...

  7. linux常用的操作命令

    ---恢复内容开始--- 最近换了工作之后,需要管理linux服务器的日常运行和维护,自然linux命令是少不了的,切换目录,vim操作等的简单的操作就不说了,有些时候还需要查看日志和监控服务器启动进 ...

  8. mysql——数据库存储引擎

    MyISAM INNODB myISAM.innodb.memory MyISAM对事务要求不高,以查询和添加为主,考虑使用. 如bbs中的发帖表,回复表: INNODB存储引擎 对事务要求高,保存的 ...

  9. 设置IDEA中各种线条颜色

    在IDEA之中, 如同TearLine, 行号, 搜索结果高亮, Caret Line, 方法分隔线颜色的设置的操作方法如下: 操作路径: 1.Ctrl+Alt+S, 打开设置:   settings ...

  10. 【JOISC2012 / bzoj4388】Invitation

    Description link Solution 可以发现题目在模拟 Prim 算法求最大生成树的过程,树边故答案与起点 \(C\) 无关. 先把所有区间离散化,注意对于一个区间 \([l,r]\) ...