EventBus源代码深度剖析
来源:互联网 发布:pscs3软件百度云 编辑:程序博客网 时间:2024/06/16 11:57
- 分析之前的思考
- 4个重要的数据结构
- 注册时的行为
- post时的行为
- 解注册时的行为
- 缓存处理
- 可以优化的地方
本分析基于的代码可以在这里clone到,强烈建议边看代码边浏览本文,本文章主要分析eventbus的核心思想,因为版本差异,有一些细节可能不太一样,但我建议各位不要陷入细节无法自拔,站在较高角度去吃透它的思想才是我们的目的,因此,一些基本概念已经有很多文章已经讲过,下文不再赘述
分析之前的思考
- eventbus的用法大家都很清楚,请看下面一种最简单的情况(一定要对照着上述地址的代码看,一些用法和当前版本的EventBus有名称上的差异),我们很清楚,在PostActivity.java中当调用了
Bus.getDefault().post("Hello EventBus");
这句代码后,RegisterActivity类中的onThreadEvent会被调用,而且是在子线程中.
// 在RegisterActivity.java类中的逻辑// onCreate函数中Bus.getDefault().register(this);// onDestroy函数中Bus.getDefault().unRegister(this);//一个接收函数 @BusReceiver(mode = EventMode.Thread) public void onThreadEvent(final String event) { Log.v(TAG, "onThreadEvent=" + Thread.currentThread().getName()); appendLog("onThreadEvent event=" + event + " thread=" + Thread.currentThread().getName()); } // 在PostActivity.java类中的逻辑 // onCreate函数中 Bus.getDefault().post("Hello EventBus");
- 我们不妨思考一下,如果是我们自己实现,我们需要考虑哪些问题,我在下面列出了一部分我们需要考虑的核心问题
- 我们需要保存注册类的哪些信息,该如何保存?
- 我们需要保存参数的哪些信息,该如何保存?
- 我们该如何确定哪些函数会被调用?
- 我们该如何控制他们在特定的线程中运行?
- 我们需不需要考虑缓存的功能?如何提升框架的性能?
- 我们接下来就一个一个解决上述的问题,再强调一遍,此时你一定要有一份完整的源代码,我的分析只能作为一个引子,讲解核心的思想,之后还需要你深入代码中把它融会贯通
4个重要的数据结构
- 标识注解结构非常基础,直接看代码,Bus.EventMode是个枚举类型,有三种值,Sender,Main,Thread,分别标识着不同的线程
@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface BusReceiver { // 默认回调的方法在主线程中执行 Bus.EventMode mode() default Bus.EventMode.Main;}
- 先解决一个重要的问题,我们在存储什么?该如何存储,我们存储的是有特殊注解的方法(Method对象),这个方法中的参数,注册的对象以及相应的注解,所以我们拼装出来了第一个数据结构
MethodInfo
public class MethodInfo { // 方法对象 public final Method method; // 注册对象的Class对象 public final Class<?> targetType; // 参数的Class对象 public final Class<?> eventType; // 没有什么关键的作用 public final String name; // 注解的mode值 public final Bus.EventMode mode; public MethodInfo(final Method method, final Class<?> targetClass, final Bus.EventMode mode) { this.method = method; this.targetType = targetClass; this.eventType = method.getParameterTypes()[0]; this.mode = mode; this.name = targetType.getName() + "." + method.getName() + "(" + eventType.getName() + ")"; } // else codes}
- 再封装一层,做到最大化解耦,让MehodInfo只负责存储相应数据结构的职责,让Subscriber去做具体的执行功能,请注意invoke函数
class Subscriber { public final MethodInfo method; public final Object target; // 我认为下面4个成员变量是多余的,因为可以从MethodInfo中拿到 public final Class<?> targetType; public final Class<?> eventType; public final Bus.EventMode mode; public final String name; public Subscriber(final MethodInfo method, final Object target) { this.method = method; this.target = target; this.eventType = method.eventType; this.targetType = method.targetType; this.mode = method.mode; this.name = method.name; }// 使用反射调用注册类的相应函数,传递进来的event是post的内容 public Object invoke(Object event) throws InvocationTargetException, IllegalAccessException { return this.method.method.invoke(this.target, event); } // else codes}
- 请注意,Subscriber只是负责了调用函数,但是没有去负责该在哪个线程中去执行,所以说,需要再封装一层,EventEmitter,它是一个Runnable对象,所以我们就可以使用Handler,Executor,控制它到底在哪个线程中执行!
public class EventEmitter implements Runnable { private static final String TAG = Bus.TAG; public final Bus bus; public final Object event; public final Subscriber subscriber; public final Bus.EventMode mode; public final boolean debug; public EventEmitter(final Bus bus, final Object event, final Subscriber subscriber, final boolean debug) { this.bus = bus; this.event = event; this.subscriber = subscriber; this.mode = subscriber.mode; this.debug = debug; } @Override public void run() { try { if (debug) { Log.v(TAG, "sending event:[" + event + "] to subscriber:[" + subscriber + "] at thread:" + Thread.currentThread().getName()); } subscriber.invoke(event); } catch (Exception e) { if (debug) { Log.e(TAG, "sending event:[" + event + "] to subscriber:[" + subscriber + "] failed, reason: " + e, e); } } } @Override public String toString() { return "{" + "event:[" + event + "] to subscriber:[" + subscriber + "]}"; }
看完四个数据结构后,感叹它巧妙的设计,利用层层包装解耦,符合单一职责的原则,使结构清晰易于维护
注册时的行为
Bus.getDefault().register(this)
- 注册时我们主要做了两件事情
- 第一件事,以注册类的完整类名为key,它其中的所有满足条件的方法为Value,存储到一个集合中
final static Map<String, Set<MethodInfo>> sMethodCache = new ConcurrentHashMap<String, Set<MethodInfo>>();
第二件事以参数类型为key,符合的方法集合为Value存储为一个ConcurrentHashMap(以方法参数为标准分割函数)
private final Map<Class<?>, Set<Subscriber>> mSubscriberMap;
我们来看一下这个过程中的重要的方法
//获得注册对象中所有符合条件的函数,包括父类中的 private Set<MethodInfo> getMethods(Class<?> targetClass) { // 完整的类名作为缓存的key String cacheKey = targetClass.getName(); Set<MethodInfo> methods; // 先看缓存是否有,如果没有去类中找寻符合要求的方法,然后在存储到缓存中 synchronized (Cache.sMethodCache) { methods = Cache.sMethodCache.get(cacheKey); } if (methods == null) { /** * 找对象以及对象父类的符合要求的方法 */ methods = mMethodFinder.find(this, targetClass); synchronized (Cache.sMethodCache) { Cache.sMethodCache.put(cacheKey, methods); } } return methods; }
- 跟入,看
mMethodFinder.find(this, targetClass);
的具体实现,代码很简单,请去MethodHelp.java类中查看,做一个简单的总结,此类的作用就是筛选出符合要求的Method拼装为MethodInfo集合,注意挑选的规则有以下几条(在isValidMethod函数中)- 首先它必须拥有BusReceiver注解,这个是大前提,之后需满足以下四个小条件
!Modifier.isPublic(method.getModifiers())
函数必须是public的Modifier.isStatic(method.getModifiers())
函数不是静态的method.getParameterTypes().length != 1
函数只能有一个参数!Modifier.isVolatile(method.getModifiers())
这个条件很重要,是修复getDeclaredMethods存在的一个bug,请参考这里
public static Set<MethodInfo> findSubscriberMethodsByAnnotation( final Class<?> targetClass) { final MethodConverter converter = new MethodConverter() { @Override public MethodInfo convert(final Method method) { // check annotation final BusReceiver annotation = method.getAnnotation(BusReceiver.class); if (annotation == null) { return null; } if (!isValidMethod(method)) { return null; } return new MethodInfo(method, targetClass, annotation.mode()); } }; return findSubscriberMethods(targetClass, converter); } /** * 根据传入的相应对象的class对象,去检索"那些要被回调的方法",存储起来,并且父类方法都要存储,其实就是加了一个while循环, * 内部再加入一个for循环 */ public static Set<MethodInfo> findSubscriberMethods( final Class<?> targetClass, MethodConverter converter) { Class<?> clazz = targetClass; final Set<MethodInfo> methods = new HashSet<MethodInfo>(); while (!shouldSkipClass(clazz)) { final Method[] clsMethods = clazz.getDeclaredMethods(); for (final Method method : clsMethods) { final MethodInfo methodInfo = converter.convert(method); if (methodInfo != null) { methods.add(methodInfo); } } // search more methods in super class clazz = clazz.getSuperclass(); } return methods; }
post时的行为
- 存储以参数类型全称为key(如java.lang.String),此参数类型的父类型或者接口类型为value(父类型和接口类型也加入是因为考虑到了兼容性,这样不需要严格的匹配参数就可以调起函数)
以参数类型为key的原因是因为eventbus确定调用哪个方法就是由参数类型而不是函数名确定的,所以说只要你post时的参数与你声明的回调函数的参数兼容,就可以调用!
final static Map<String, Set<Class<?>>> sEventTypeCache = new ConcurrentHashMap<String, Set<Class<?>>>();
- 获得了参数类型的所有兼容的类型后,一次传递需要传递的值和相应的兼容的类型
for (Class<?> eventType : eventTypes) { postEventByType(event, eventType); }
- 跟入postEventByType函数
/** * 发送某个事件给某个特定类型的订阅者 * * @param event 事件对象 * @param eventType 匹配的事件类型 * @param <E> 事件类型 */ private synchronized <E> void postEventByType(final E event, final Class<?> eventType) { final Set<Subscriber> subscribers = mSubscriberMap.get(eventType); if (subscribers == null || subscribers.isEmpty()) { return; } for (Subscriber subscriber : subscribers) { sendEvent(new EventEmitter(this, event, subscriber, mDebug)); } }
- 前面说过EventEmitter的类型和作用,继续跟入sendEvent方法,此方法的作用是根据注解的模式选择相应的类去执行emitter的run方法,此方法逻辑比较简单,请去代码中查看Schedulers类,它会用工厂模式分别创建三个子类,SenderScheduler(直接调用emitter的run方法,以同步方式执行)负责在当前线程中执行,HandlerScheduler(依赖Handler)负责在主线程中执行,ExecutorScheduler(依赖Executor)负责在异步线程中执行,到最后就都会执行到Emitter的run方法中
public void sendEvent(EventEmitter emitter) { if (mDebug) { Log.v(TAG, "send event:" + emitter); } if (EventMode.Sender.equals(emitter.mode)) { mSenderScheduler.post(emitter); } else if (EventMode.Main.equals(emitter.mode)) { if (Helper.isMainThread()) { mSenderScheduler.post(emitter); } else { mMainScheduler.post(emitter); } } else if (EventMode.Thread.equals(emitter.mode)) { mThreadScheduler.post(emitter); } }
- 看一下emitter的run方法,非常简单,到最后都会执行过来,只是过来的途径不同(上面分析的三个类负责),由下面可以看到是最终其实执行的是Subscriber的invoke方法,我们上面分析过,这个方法是用反射去调相应的method,到此就分析完毕了,最终就会成功的调用注册类中相应的方法了
@Override public void run() { try { subscriber.invoke(event); } catch (Exception e) {} }
解注册时的行为
- 不知道大家有没有想过这个问题,post时,调用哪个方法是基于参数去判断的,那么,当我们解注册的时候,如何去确定解注册的那个类中的所有方法,我们需要把这些方法从mSubscriberMap中移除,要保证精确性而不能移除错误,因此,又多出来一个数据结构去做这件事情,它的key就是我们的类对象,它的value就是我们类对象中符合方法的所有的参数对象(想想为什么把参数对象作为value,而不是把方法对象作为value,因为我们有mSubscriberMap,可以根据参数对象取出方法对象,再通过方法对象Subscriber的target属性去和我们当前要解注册的对象对比,如果一致,那么把此方法对象从mSubscriberMap中删除)
private final Map<Object, Set<Class<?>>> mEventMap;
public <T> void unregister(final T target) { if (mDebug) { Log.v(TAG, "unregister() target:" + target); mStopWatch.start("unregister()"); } //mEventMap的添加元素逻辑在register方法中 final Set<Class<?>> eventTypes = mEventMap.remove(target); if (eventTypes == null || eventTypes.isEmpty()) { Log.v(TAG, "unregister() no subscriber for target:" + target); return; } for (Class<?> eventType : eventTypes) { Set<Subscriber> subscribers = mSubscriberMap.get(eventType); if (subscribers == null || subscribers.isEmpty()) { continue; } synchronized (mSubscriberMap) { Iterator<Subscriber> it = subscribers.iterator(); while (it.hasNext()) { final Subscriber subscriber = it.next(); if (subscriber.target == target) { it.remove(); } } } } } }
缓存处理
- events的性能瓶颈在两方面有体现.
- 第一,就是我们去查找某个类中符合特定要求的方法并存储,因为我们不止要查找当前类,还需要查找父类,所有这是一笔不小的开销
- 第二,我们要去把所有兼容的参数类型查找并存储,这也是一笔不小的开销
- 根据以上的描述,我们就用了两个数据结构把他们在第一次search时缓存了起来,一个是完整的类名和对应的所有符合要求的方法,另一个是完整的参数名和所有兼容的类型
static class Cache { // key=注册类的完整类名 target.getClass().getName() // value=注册类包含的合法的@BusReceiver方法对象集合 // targetTypeName->method set final static Map<String, Set<MethodInfo>> sMethodCache = new ConcurrentHashMap<String, Set<MethodInfo>>(); // key=事件类型的完整类名 // value=事件类型的所有父类和接口 // eventTypeName-> event type set final static Map<String, Set<Class<?>>> sEventTypeCache = new ConcurrentHashMap<String, Set<Class<?>>>(); }
- 它俩使用和填充位置分别在register()和unRegister()函数中,请在AndroidStudio中自己使用command + F和command + G自行查看.
可以优化的地方
- 当我们去搜寻一个类中符合要求的方法时,其实没有必要搜寻系统级别的类的方法,比如Object类中的方法,那么我们就可以做一些小小的优化,请看下面代码,代码很简单,不做赘述
public static boolean shouldSkipClass(final Class<?> clazz) { if (clazz == null || Object.class.equals(clazz)) { return true; } final String clsName = clazz.getName(); return clsName.startsWith("java.") || clsName.startsWith("javax.") || clsName.startsWith("android.") || clsName.startsWith("com.android."); }
1 0
- EventBus源代码深度剖析
- 深度剖析memcached源代码线程模型
- JavaSE第五十二讲:HashSet 与HashMap源代码深度剖析
- android EventBus深度讲解
- Android EventBus事件总线剖析
- 深度剖析WinPcap之(序言)——分析WinPcap源代码的缘由
- 深度剖析WinPcap之(序言)——分析WinPcap源代码的缘由
- 深度剖析WinPcap之(序言)——分析WinPcap源代码的缘由
- Tomcat根据JSP生成Servlet机制深度剖析及核心源代码详解
- EventBus的使用和原理剖析
- EventBus的使用和原理剖析
- EventBus的使用和原理剖析
- 拆轮子系列之剖析EventBus源码
- 工具条源代码剖析
- NFS 文件系统源代码剖析
- NFS 文件系统源代码剖析
- C# MemoryStream源代码剖析
- NFS 文件系统源代码剖析
- C语言学习3
- tolua++ lib 和 exe的生成
- String类型出生年月计算年龄
- leetcode Maximum Depth of Binary Tree
- poj 2632 Crashing Robots
- EventBus源代码深度剖析
- springmvc图片上传到虚拟目录
- java web和Android第一次交互(登录)
- jenkins中jelly文件的使用
- Android ImageView的scaleType属性与adjustViewBounds属性
- CSU1513 Kick the ball! 湖南10届省赛 dfs
- SIFT特征提取分析
- Android 数字签名
- TCP滑动窗口协议及拥塞控制