小记
之前在公司写的一个Demo,埋头只顾着实现需求,本想着一个Demo 就演示给别人看看的也没必要写多好,等到时候确认要的时候再去搞的好一点,谁知道后期直接就从demo要变成开发代码。我这一手MVC写的代码如何是好,想改成MVP吧时间上又有点赶紧,最后搞了一个EventBus来假装自己是MVP。具体就是把所有的逻辑放到一块去处理,然后根据Type去区分逻辑的处理,最后通过EventBus 通知到对应的页面。还是要养成敲代码的好习惯。
EventBus的使用
这是官方的地址,依赖,使用方法说的很清楚,这里就不多说。
https://github.com/greenrobot/EventBus
偷张图
![](https://img.haomeiwen.com/i6650292/fc17d9f2387d7318.png)
Publisher post 出来消息,然后进入EventBus这个黑盒,然后就转给了Subscriber,谁订阅了 就给谁返回,完成了一次Event的传递。
EventBus的实现
今儿就对EventBus的这个中间的黑盒进行研究。结合他的代码。首先来看一下EventBus的成员。
![](https://img.haomeiwen.com/i6650292/4e4e4f425489f169.png)
1. getDefault() 这显然是个单例,然后调用了regist()方法 和 unregister()这两个方法。这里有个注意点就是register()和unregister这两个方法里面的参数都是Object,很多时候在Activity中用到很多的this都是当context 来用的,带来的问题就是在fragment 中其实传入的也要是页面本身,即当前的Fragment,这种就是错误的
EventBus.getDefault().register(mContext);
正确的为
EventBus.getDefault().register(this);
2. @SubScirbe
这是一个JAVA注解 点进去一看
![](https://img.haomeiwen.com/i6650292/88a7878f88380e29.png)
里面又冒出个ThreadMode , default ThreadMode.POSTING;点击查看之后又是一个枚举,然后后面两个Stick和Priority分别代表是否是粘性事件跟优先级。
3. ThreadMode 其实就有几种线程模式。点击可以直接查看一下一共有几种还有介绍。查看之后发现就是POSTING,MAIN等。
4. 最后就是post这个方法里面是一个Object类相当于把所需要发的数据丢出去,官方意思是写一个实体因为这能发送多个数据,都在一个实体里面。这里也不支持多个参数的方法,在后面介绍就知道是为什么了。
更多详细的EventBus为什么实现可以看一下源码。下面开始抄袭。
实现简单的EventBus
首先回顾一下上面的流程基本上可以理解为 注册--->post--->@Subscriber方法处理--->取消注册。
-
线程类型
就是在什么线程进行回调,ThreadMode 这里是个枚举。这里只写两,实际还有BACKGROUND等。public enum ThreadMode { POSTING, MAIN }
-
Subscriber注解
搞这个注解就是为了方便后面回调这个方法方便。可以理解为一个标记,通过@Subscriber来进行方法的查找。@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Subscribe { ThreadMode threadMode() default ThreadMode.MAIN; }
@Target 代表的是在什么上面使用,@Retention代表的是在什么时候使用。
-
存储实体 SubscribeMethod(随便取的名字)
里面有一个ThreadMode,一个Method(后面调用.invoke方法用的),还有一个Class,即自己的类型,用实体对象来存所有register过的Object。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; } //get() Set() 方法省 }
-
EventBus方法
首先是单例private static volatile MyEventBus instance; private MyEventBus() { cacheMap = new HashMap<>(); } public static MyEventBus getDefault() { if (instance == null) { synchronized (MyEventBus.class) { if (instance == null) { instance = new MyEventBus(); } } } return instance; }
其次看一下register方法
private Map<Object, List<SubscribeMethod>> cacheMap;
public void register(Object object) {
List<SubscribeMethod> list = cacheMap.get(object);
//判断是否 存过
if (list == null) {
list = findSubcirbeMethod(object);
cacheMap.put(object, list);
}
}
首先准备了一个map ,key为 register进来的对象,value 为SubscribeMethod列表,List<SubscribeMethod> list = cacheMap.get(object);先取目的是为了去除重复的,假如不为null 说明已经register 不重复注册。findSubcirbeMethod(Object obj)方法来进行查找这个Object中有多少个被@Subscriber注解的方法,全部取出来进行分类。
private List<SubscribeMethod> findSubcirbeMethod(Object object) {
List<SubscribeMethod> list = new ArrayList<>();
Class<?> clazz = object.getClass();
//这个只负责自己的方法 getMethod 包含父类的方法
Method[] methods = clazz.getDeclaredMethods();
Log.e(TAG, "findSubcirbeMethod: " + methods.toString());
//寻找带有注解的的Subscribe的注解
while (clazz != null) {
//去除系统的包里面的东西
String name = clazz.getName();
if(name.startsWith("java.")||name.startsWith("javax.")||name.startsWith("android.")){
break;
}
for (Method method : methods) {
//这里选得到的是哪个
//override 为什么没有用 因为RetentionPolicy.SOURCE 无效
//取出注解的@Subscribe的方法
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe == null) {
//为空就继续
continue;
}
//判断带有subScribe中的参数类型
//这里判断的是 返回的参数类型,一般是一个实体,所以不能搞两个参数的。省得麻烦。
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) {
Log.e(TAG, "findSubcirbeMethod: type!=1+ Exception");
}
//获取线程的模式
ThreadMode threadMode = subscribe.threadMode();
//构建SubscribeMethod 对象,传入方法,线程模式,构造参数。
SubscribeMethod subscribeMethod = new SubscribeMethod(method, threadMode, types[0]);
//添加到列表 因为一个页面可能有多个
list.add(subscribeMethod);
}
//父类的查找,防止写一些BaseActivity
clazz = clazz.getSuperclass();
}
return list;
}
这里得到的是一个List<SubscribeMethod>,下面看一下post(Object obj )方法:
public void post(final Object object) {
//直接从cache里面取出就行
Set<Object> set = cacheMap.keySet();
Iterator<Object> iterator = set.iterator();
while(iterator.hasNext()){
final Object obj = iterator.next();
List<SubscribeMethod> list = cacheMap.get(obj);
for(final SubscribeMethod subscribeMethod:list){
/**
* 这里判断的是 subscribeMethod.getType() 是不是等于 post里面的"实体",
* 等于才能发送,一个Activity中完全可能有多个@Subscribe注册的方法。
*/
if(subscribeMethod.getType().isAssignableFrom(object.getClass())){
switch (subscribeMethod.getThreadMode()){
//这里判断线程是什么,然后进行对应的线程切换,用什么切线程各显神通。Handler ,Rx 都可以。
case MAIN:
if(Looper.myLooper() == Looper.getMainLooper()){
Log.e(TAG, "post: 主线程");
notifySetDataChanged(subscribeMethod,obj,object);
}else{
Log.e(TAG, "post: 子线程");
handler.post(new Runnable() {
@Override
public void run() {
notifySetDataChanged(subscribeMethod,obj,object);
}
});
}
break;
case POSTING:
//切换到子线程
break;
}
}
}
}
}
notifySetDataChanged方法:
private void notifySetDataChanged(SubscribeMethod subscribeMethod, Object obj, Object object) {
//获取到方法。
Method method = subscribeMethod.getMethod();
try {
//method.invoke() 见 https://blog.csdn.net/weixin_42124622/article/details/81909180
method.invoke(obj,object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
这样就可以实现EventBus 的效果。
后记
其实这个EventBus 其实还有很多的内容,这里只是写了一个大概,具体还有很多的功能没有做,只是记录一下学习的过程,看别人开源的框架,然后再抄一个出来的话,其实还是有不少好处的,一个好的开源框架着实用到了很多平时开发中不常使用的东西,比如这个EventBus用到了反射跟注解,正好可以再学习一波巩固一下知识点。后面有时间再来补充其他的EventBus里面的实现内容。
学习资料来自于网易公开课。
网友评论