一、前言:
EventBus是一款事件发布与订阅的框架,主要用来替代Intent、Handler、BroadCast来实现组件通信,线程间通信。优点是:开销小,代码优雅,以及将发送者与接收者解耦,使用非常简便。分析EventBus的博客很多,但是很少有带大家去实现一款EventBus框架的。没错,这里我将带你实现一款自己的EventBus框架。不管你之前是否接触过EventBus,都可以通过学习而本篇文章,来学习EventBus。
二、准备
在看这篇博客之前需要对反射、注解有一定的认识,否则你是不容易看懂的。
三、流程
整个EventBus发布订阅的流程可以形象为以下过程,包括后面的部分都是以这个流程来分析的
1、订阅过程:
假设某公司要招聘Android、IOS、前端、Java...程序员,它会先在招聘中介网站上(比如“前程无忧”)提交这些信息。而“前程无忧”需要记下这些信息,它会将该公司招聘的所有岗位存入一个List<招聘岗位>集合中,因为招人的公司肯定不止这一家,所以然后"前程无忧"会以公司为键,招聘岗位List为值存入一个Map<公司,List<招聘岗位>>中。这就是一个订阅过程、招聘的公司就是订阅者、“前程无忧”就是一个中介。
2、发布过程:
求职者为了找工作,也会去“前程无忧”上面发布找工作的消息,假设我们找的是Android的工作,“前程无忧”就会去它的Map<公司名,List<招聘岗位>>中找,看有哪个公司是招聘Android的,找到之后就会把Android求职推荐给该公司。
3、注销过程:
假设公司不需要再招人,那么怎么办呢?那就注销呗,此时“前程无忧”就会在它的Map<公司名,List<招聘岗位>>中,将该公司移除掉。
上面的过程大概可以用下面这张图来表示:
理清思路之后我们就可以来撸码了。
四、正式撸码
1、先看下我们所写代码的整体结构:
![
没错,就这四个类就是实现了一款EventBus,麻雀虽小,但也是五脏俱全。EventBus类就是“前程无忧”,SubscribleMethod类就是招聘岗位,ThreadMode和Subscrible是啥先别管它。
2.EventBus类的基本实现(前程无忧)
求职者要找工作,公司要招人,那么肯定得先有一个招聘的招聘的中介"前程无忧"。那么我们现在就来写一个中介EventBus类,至于怎么写了?大家在使用EventBus的时候都知道使用以下这句代码进行注册(订阅):
EventBus.getDefault().regist(this);
那么我们就依葫芦画瓢,写一个getDefault()
方法,代码如下:
/**
* Created by Administrator on 2017/5/22.
* 招聘中介
*/
public class EventBus {
//招聘中介的信息表(表里面填了各大公司需要招聘的岗位)
//Map<招聘公司,该公司招聘的岗位集合>
private Map
代码行数不多,采用了一个双重检查锁式的单例模式来创建EventBus的实例。cacheMap就是用来存储各公司招聘信息的。SubscribleMethod这个类就是招聘岗位信息的封装类,暂时不用管它,咱们后面再说。
2、注册方法的实现()
有了EventBus的实例,那么我们就来实现regist方法,代码如下:
/**
* 注册
* @param obj 公司
*/
public void regist(Object obj){
List list = cacheMap.get(obj);
//还没有注册过,那么就将岗位集合put进中介的map中,
//如果已经注册过了,那么久不用再次注册,那么什么都不做
if(list==null){
//找到公司obj招的所有岗位的集合
List methods = findSubscribleMethod(obj);
cacheMap.put(obj,methods);
}else {
throw new RuntimeException("你就已经注册(订阅)过了,不需要再次注册(订阅)");
}
}
上面这个注册方法,参数就是要注册的公司对象。首先会去“前程无忧”的Map集合里面找一下,看是否这个公司已经注册(订阅)过了,如果已经注册(订阅)过了,那就抛一个异常告诉它不需要再注册了,如果没有注册(订阅),才让他注册(订阅),注册的公司obj需要招人,那么就得知道obj这个公司到底招聘那些岗位,findSubscribleMethod(obj)这个方法就是找到公司obj的所有招聘岗位(具体实现咱们后面再说,你现在只要知道它是干啥的就行),找到公司obj所有岗位的集合后,后面的自然是将公司招聘信息存入“前程无忧”的招聘信息表Map里面去了, 也就是这句代码cacheMap.put(obj,methods)。
2、Subscrible是个啥?
上面的代码结构图中出现Subscrible,那它是个啥呢?我们现在就来说一说,代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscrible {
ThreadMode value() default ThreadMode.PostThread;
}
没错,它就这几行代码,灰常简单,可以看出它就是一个注解, 简单的来说,招聘的公司招人它得有个招聘的方法呀,招聘的公司肯定也得干其他的事,肯定也有非招聘的方法,那么怎么知道它的哪些方法是招聘的方法呢?那么就给它的招聘方法上面加个Subscrible注解我们就知道了,里面的value()先不管它,下面先给个示例:
/**
*公司招人的方法
* @param content
*/
@Subscrible(ThreadMode.PostThread)
public void receive(AndroidProgrammer content){
Log.i("zkx","Thread "+Thread.currentThread().getName());
//Toast弹不出来,不知道为啥
//Toast.makeText(this,Thread.currentThread().getName(),Toast.LENGTH_SHORT);
}
@Subscrible(ThreadMode.MainThread)这句代码就表示了这个方法是一个招聘方法,()里面的代码表示这个方法是在主线程里面执行的。该方法的参数类型就表明了招的是什么职位,我这里是招聘AndroidProgrammer,至于ThreadMode请看下面。
3、ThreadMode是个啥?
ThreadMode听名字就知道是线程模式的意思,具体的请看代码:
public enum ThreadMode {
PostThread,//发送线程执行
MainThread,//主线程执行
BackgroundThread,//后台线程执行
Async
这段其实就是直接复制EventBus源码里面的,英文注释太长了,我给去掉了。可以看出他就是一个枚举,请看我给出的注释。再回到
注解Subscrible的代码中,我们就可以知道它里面的value()值就是说加上这个注解的在哪个线程执行了,我的代码中默认指定的是PostThread。
4、SubscribleMethod是个啥?
从名字是可以看出它是订阅方法的意思,实际是上它是封装了招聘岗位信息(公司招聘的方法、招聘岗位的类型、招聘方法在哪个线程执行)的一个JavaBean。具体代码如下:
**
* Created by Administrator on 2017/5/22.
* 招聘岗位信息类
*/
public class SubscribleMethod {
//招聘这个岗位的方法
private Method method;
//招聘这个岗位的方法在哪个线程里面执行
private ThreadMode threadMode;
//招聘的岗位的类型
private Class> eventType;
public SubscribleMethod(Method method, ThreadMode threadMode, Class> eventType) {
this.method = method;
this.threadMode = threadMode;
this.eventType = eventType;
}
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> getEventType() {
return eventType;
}
public void setEventType(Class> eventType) {
this.eventType = eventType;
}
}
5、深入细节
到这里EventBus的各个类的意思已经说完了,我们来看一些细节的东西。上面“2、注册方法的实现”这段,我们说到findSubscribleMethod(obj)这个方法就是找到公司obj的所有招聘岗位,那么我们就来看看它是怎么找的,代码如下:
/**
* 寻找该公司的招聘岗位
* @param activity 该公司
* @return
*/
private List findSubscribleMethod(Object activity) {
List list = new CopyOnWriteArrayList<>();
Class> clazz = activity.getClass();
Method[] methods = clazz.getDeclaredMethods();
//循环查找父类 接收方法
while(clazz!=null){//如果该公司又隶属于某个更大的公司,那么它也要为这个更大的公司招人
String name = clazz.getName();
//系统方法直接break
if(name.startsWith("java.")||name.startsWith("javax.")||name.startsWith("android.")){
break;
}
//遍历所有类的方法(其中有Subscrible注解的为招人的岗位(方法))
//每找到一个招聘岗位就将其放到集合中,最终返回的集合list就是该公司所有招聘岗位的集合
for (Method method:methods){
//持有Subscrible注解的才是个接收方法,才是个岗位
//发生线程
Subscrible subscrible = method.getAnnotation(Subscrible.class);
if (subscrible==null){
continue;
}
//拿到方法的参数类型的数组
Class>[] parameters = method.getParameterTypes();
if(parameters.length!=1){//只能接收一个参数
throw new RuntimeException("eventbus must be one parameter");
}
//获得参数类型数组的第一个元素(也只有一个元素)
Class> paramClass = parameters[0];
//拿到发生的线程(默认发生在PostThread)
ThreadMode threadMode = subscrible.value();
//new一个招聘岗位
SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,paramClass);
//招聘中介(前程无忧) 把该公司所有岗位缓存到集合
list.add(subscribleMethod);
}
clazz=clazz.getSuperclass();
}
return list;
}
从代码总可以看出,我们其实就是通过反射拿到该招聘公司的所有方法,遍历这些方法,判断这些方法是不是招聘的方法,具体的就是看这些方法有没有加Subscrible 注解,加了注解的肯定就是招聘方法,继而拿到,每一个招聘方法的参数类型,也就是招聘的岗位,然后将所有的岗位存入List集合中。还有一点要注意的是如果公司类是继承自某个类,也就是说该公司它是某个更大的公司的一个子公司,那么它在招人的同时也得给它的父公司招人。所以有了我们的最外层的while循环,不断遍历它的父类,直到系统类为止。
6、发布的方法
到这里,中介有了,公司也注册(订阅)了,那么就到我们求职者出场了,假设我们要找Android程序员的工作,那么就只需要调用下面这句代码就行:
EventBus.getDefault().post(new AndroidProgrammer("zkx","123456"));
其实就是调用了EventBus的post方法,具体实现如下:
/**
* 安卓程序员 来找工作
* @param object 求职的岗位类型
*/
public void post(final Object object) {
//拿到集合cacheMap里所有的公司
Set
代码有些长,但是基本上每行代码都给出了注释。理解起来应该不难。大体的流程就是在”前程无忧”的信息表里面拿到所有的公司,然后遍历这些公司,看这些公司的招聘岗位里面有木有Android的岗位(是否匹配),如果有的话,那就利用反射去调用有招聘Android岗位的公司的招聘方法来招聘这个求职者。还有需要注意的就是,我们会根据发布方法(求职者发布求职的方法)和接收方法(公司招人的方法)所在线程的不同,采取不同的策略。具体请看代码,有详细的注释。反射调用公司的招聘方法代码如下:
/**
* @param activity 公司
* @param method 岗位信息封装类
* @param object 求职者
*/
private void invoke(Object activity, SubscribleMethod method, Object object) {
try {
//调用公司activity的岗位method的招人方法method.getMethod()方法,
//传参object,代表招Object类型岗位的人
method.getMethod().invoke(activity,object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
7注销方法的实现(取消订阅)
公司不招人了,那么就得注销(取消订阅),代码很简单,就是将该公司的信息从EventBus的map中移除就行了
public void unregist(Object object){
//在中介的map中将该公司object移除
cacheMap.remove(object);
}
五、最后
到这里我们所有的代码就写完了,现在就可以使用我们写的这款框架来进行组件间、线程间通信了,写的不好的或者错误的,欢迎批评指正。
全部代码下载地址:
https://github.com/zkxok/MyEventBus