EventBus3 源码解析(个人理解) 之一
来源:互联网 发布:淘宝打不开微信链接 编辑:程序博客网 时间:2024/06/07 10:06
转载请说明地址: http://blog.csdn.net/it_peng/article/details/51581426
前言:
还记得我曾经在知乎上面提问:说怎么看源码,什么样的姿势比较好?
但是也很少人回答我。但是也没有关系。原来我不知道怎么回答,现在
估计能回答一点点了。
姿势_1:
就拿EventBus框架来说吧!我们首先应该会用是吧.
基本功能:
注册: EventBus.getDefault().register(this);
反注册: EventBus.getDefault().unregister(this);
发送: EventBus.getDefault().post(Object xxx);
接收:
@Subscribe public void onEventMainThread(Object xxx) { //逻辑处理。}
这里特别提醒一点:Evenbus3 不需要必须这onEvent开头的方法。(至于为什么我们下面解析)
姿势二:
1.先看别人解析。
2.好奇心。
3.敢于探索。
4.敢于发出疑问
为什么 先看别人解析啦? 其实我们自己分析一般都不是特别到位了,这个时候我们应该找一找有没有大牛们分析过的文章看一下,先知道大概的思路是怎么样的,一边看一遍想。然后自己看多看几遍。然后一点要多动手。
————————————不多说了,开始分析了————————————————-
这里我们先提出几个问题带着问题思考:
1.注册的时候发生了什么?
2.为什么我只需要 @Subscribe 注解声明一下就可以了,不用规范方法名称(我说的规范指的是onEvent这样规则命名方式)
3.post的时候怎么就可以发送到我创建的相同参数的方法,并且执行啦?
4.为什么说直接发送到主线程不能运行太耗时的方法?
5.为什么听别人说eventbus3的运行效率貌似比原来的快了许多倍?
6. Eventbus 有哪些很好的设计?
1.注册的时候发生了什么?
/** * 注册给定的订阅服务器接收事件。用户必须调用{@链接#注销(对象)}一旦 * 不再对接收事件感兴趣。 * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they * are no longer interested in receiving events. * <p> * <p> * 用户有事件处理方法,必须注明“{ @链接订阅}。 * {“链接”订阅}注释还允许配置像{ @链接 * threadmode }和优先级。 * Subscribers have event handling methods that must be annotated by {@link Subscribe}. * The {@link Subscribe} annotation also allows configuration like {@link * ThreadMode} and priority. */ public void register(Object subscriber) { //首先获得订阅者的class对象。 Class<?> subscriberClass = subscriber.getClass(); //通过subscriberMethodFinder来找到订阅者订阅了那些事件 // ,返回一个SubscriberMethod对象的list,SubscriberMethod // 里包含了这个方法的Method对象,以及将来响应订阅是在哪个线程的ThreadMode //以及订阅的事件类型eventType,以及优先级priority,以及是否接收粘性sticky事件的boolean值. List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { //订阅。(定义数据存放规则) subscribe(subscriber, subscriberMethod); } } }
上面的注释先大概看一下就好了,
这里我们先看到 findSubscriberMethods 其实从字面的意思我们就可以知道 找到订阅者下的方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //先从Method_cache取看是否有缓存,key:保存订阅类的类名,value:保存类中订阅的方法数据。 1. List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } // 是否忽视注解器生成的MyEventBusIndex类。 默认都是false. if (ignoreGeneratedIndex) { //利用反射来读取订阅类中的订阅方法信息。 2. subscriberMethods = findUsingReflection(subscriberClass); } else { //从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息。 3. subscriberMethods = findUsingInfo(subscriberClass); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { //保存Mehod_CACHE缓存。 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }
我们主要看到 ignoreGeneratedIndex 其实默认都是false的。
不相信的话可以看到
EventBusBuilder—> ignoreGeneratedIndex 这个赋值的方法。
既然是false,那我们就看看
subscriberMethods = findUsingInfo(subscriberClass);
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { 1. FindState findState = prepareFindState(); // 复用。 2. findState.initForSubscriber(subscriberClass); //初始化 // 判断这个类是否为空。 while (findState.clazz != null) { //获取该类订阅的所有方法了。 3. findState.subscriberInfo = getSubscriberInfo(findState); //如果在加速索引没有找到值得话,那就反射吧。 4. if (findState.subscriberInfo != null) { //获取本类中的所有方法。 SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { //检测是否满足。 if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { findUsingReflectionInSingleClass(findState); } //移除系统的类。 findState.moveToSuperclass(); } //筛选出来那些合格的方法。 return getMethodsAndRelease(findState); }
我最喜欢一段代码就是 1. 2. 这两行代码了。下面我会解释为什么的。
/3. 这个只有开启了索引加速才会有实际意义。如果想知道的话可以看到 4.为什么听别人说eventbus3的运行效率貌似比原来的快了许多倍? 这一标题下
那么此时的 4. ==false。
我们就需要看到
findUsingReflectionInSingleClass 这个方法
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; //通过反射得到方法数组 try { // This is faster than getMethods, especially when subscribers are fat classes like Activities methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } //遍历Method for (Method method : methods) { //获取方法的修饰符 int modifiers = method.getModifiers(); // 后面一部分 解读忽视的 修饰符。 if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //获取参数类型。 Class<?>[] parameterTypes = method.getParameterTypes(); //保证必须只有一个事件参数。 //因为 post 方法就只能传递一个object。 if (parameterTypes.length == 1) { //得到注解(看方法有没有这个标识) //3.0 貌似必须用注解。 Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { // 如果有的话。 Class<?> eventType = parameterTypes[0]; //获取参数的类型 //校验是否添加该方法。 if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); //实例化SubscribeMethod对象并添加。 findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } // strictMethodVerification //严格查询Method的名称吗? && 判断是否有这个注解。 } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } }
step1:
获取该类的所以方法
methods = findState.clazz.getDeclaredMethods();
methods = findState.clazz.getMethods();
为什么会有两个方法的?并且为什么注释说第一个比较快啦?
第一种只获取本类的所有方法
第二种会获取父类的已经子类的所有方法。
效果图片:
step 2:
对每一个方法进行遍历筛选符合的方法。
1. 判断一:
先对方法的修饰符进行筛选:
规则是:必须是public 并且不能是 Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
2. 判断二
对方法的参数个数进行筛选:
因为post的参数就只是一个Object类型。所以两个以上参数肯定是不行的
3. 判断三
方法有没有 @Subscribe 的注解
4. 判断四
checkAdd是为了避免在父类中找到的方法是被子类重写的,此时应该保证回调时执行子类的方法。
进行层层的判断,一遍遍的筛选成功的话:
添加进入: 该类的一个方法集合中去。
如果判断不合格的话,就不解释了。直接看源码错误信息。
还有一些过程就是一异常处理,以及资源移除和重复利用。
接下来看到 register方法中的
synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { //订阅。(定义数据存放规则) subscribe(subscriber, subscriberMethod); } }
那我们就进入 subscribe 这个方法咯
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //获取订阅的属于哪个类。 Class<?> eventType = subscriberMethod.eventType; // 创建Subscription对象 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //从subscriptionsByEventType里检查是否已经添加过该Subscription,如果添加过就抛出异常。 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //如果出现重复 表示已经注册过了。 if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //根据优先级priority来添加 新的 Subscription对象 int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } //将订阅者对象以及订阅的事件保存typesBySubscriber里 //为什么要用集合啦 是因为 一个类中可以包含很多 订阅事件。 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); //分发粘行事件处理。 //可以查看源码 }
我来解释一些这些代码:
1.先获取这个方法参数的类型。
2.在根据这个参数判断是否有订阅者。
3如果原来没有的话,就以方法的参数为 key 并添加一个空的对象集合。
4.根据优先级priority来添加 新的 Subscription对象
注意: 这里是根据方法参数 来添加订阅者的。
看到这里:
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
- 获取到这个类下的所有订阅方法集合。
- 如果该类没有订阅的方法,那么生成一个空的对象集合。
以这个类为key,添加这个方法到集合中。
注意: 以当前类,获取或添加订阅的方法集合
如果读完上面的这些解析其实就明白了
1.注册的时候发生了什么?
答:获取订阅类中的所有被订阅的方法名称,方法参数类型。并且把对应的关系存放到集合。
有。1。根据方法参数 来添加订阅者的。
2 .以当前类,获取或添加订阅的方法集合
筛选 一些合法的订阅方法参数。
2.为什么我只需要 @Subscribe 注解声明一下就可以了,不用规范方法名称(我说的规范指的是onEvent这样规则命名方式)
答:因为在源码中通过注解获取到方法的参数类型以及方法。并通过方法的参数类型进行存储 订阅者的。(当然啦,如果命名有意义,那么阅读代码的速度也就更快了)
既然已经知道注册的过程了,那必然需要看到post方法啦(post 在手说到就到)
/** * Posts the given event to the event bus. */ public void post(Object event) { //得到当前线程的Posting状态 ThreadLocal 可以存储线程的值。 PostingThreadState postingState = currentPostingThreadState.get(); //初始化当前线程的存放集合。 List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); if (!postingState.isPosting) { //判断是否在主线程类使用。 postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { //一直发送。 while (!eventQueue.isEmpty()) { /** * eventQueue.remove()--》 移除第一个。 * postingState 判断是在子线程 或者 主线程 状态。 */ postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
那么就来解释一下是为什么吧?
可能大家可能很少接触 ThreadLocal 中的 ThreadLocal 这个类,其实可以这样理解
ThreadLocal是一个线程内部的数据存储类。通过它可以在指定线程中存储数据,并获取到指定线程中的数据。
如果还没有看到我原来写的 ThreadLocal 工作原理
其他的也就蛮简单直接看到下面这个方法吧
postSingleEvent();
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; //是否触发订阅了该事件的父类,以及接口的类的响应方法。 if (eventInheritance) { // ------------- 等下再来看。 //查找eventClass类所有的父类以及接口。 List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { //post 单个 subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } // 如果没有发现 if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { //发送一个NoSubscriberEvent事件,如果我们需要处理这种状态。接收这个事 post(new NoSubscriberEvent(this, event)); } } }
说一下思路:
首先获取到 发送的类型。然后判断是否触发订阅事件的父类,以及响应方法。如果 eventInheritance ==false的话
就把这个类型的发送到所有订阅这个方法的类型。
如果没有订阅者的话,就直接发送一个
new NoSubscriberEvent()类型的事件。
接下来看看:
postSingleEventForEventType(event, postingState, eventClass); 方法
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //根据这个类来查找 所有订阅在的集合。 subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; //是否被中断。 ----为什么要这样判断. boolean aborted = false; try { //分发给订阅者 postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }
其实这个方法就是获取到订阅该种类型的所有订阅者。和处理一些异常的情况。然后循环变量发送给所有订阅过的订阅者
通过
postToSubscription(subscription, event, postingState.isMainThread);
那么继续看到这个方法
//判断发送的状态。 //不能在方法中写耗时的操作,避免消息延迟。因为是按照顺序执行。 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case BACKGROUND: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
大家可能看到这么多的
case posing:
case Main:
case backgound:
case async:
这些都是注解修饰调用方法的类型
@Subscribe(threadMode = ThreadMode.BACKGROUND) public void onEventMainThread(WorkEvent event) { } @Subscribe(threadMode = ThreadMode.ASYNC) public void onEventMainThread(String event) { }
以及它们的解释:
PostThread:如果使用事件处理函数指定了线程模型为PostThread,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为PostThread的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
MainThread:如果使用事件处理函数指定了线程模型为MainThread,那么不论事件是在哪个线程中发布出来的,该事件处理函数都会在UI线程中执行。该方法可以用来更新UI,但是不能处理耗时操作。
BackgroundThread:如果使用事件处理函数指定了线程模型为BackgroundThread,那么如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
Async:如果使用事件处理函数指定了线程模型为Async,那么无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行。同样,此事件处理函数中禁止进行UI更新操作。
其实分析我们就会好奇我为什么怎么调用它,怎么才能触发这个方法,并得到执行?
答案:必然是反射的使用
我们看到调用的方法。
void invokeSubscriber(Subscription subscription, Object event) { try { // 还说不是反射。调用的时候必然反射。 subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } }
还记得我们说过为什么不能再回调的方法中超长时间的处理吗?
我们来看一个效果:
//效果图
//———————–
有没有看到其实这个时候我就是模拟队列请求。等一个个参数都按照顺序执行。
也正是因为顺序执行遇到第一个方法运行时间过长,就会导致后续的事件延迟发送。这个时候也可以用异步的方式发送,不过需要用handle 配合更新UI。
这也解决了这个问题 4.为什么说直接发送到主线程不能运行太耗时的方法?
还有那个问题 我们开启下一篇去写吧?我想把细节什么都写清楚啦。
- EventBus3 源码解析(个人理解) 之一
- EventBus3 源码解析(个人理解) 之二。
- EventBus3.0 源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- Eventbus3.0 源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- EventBus3.0源码解析
- EventBus3.0最容易理解的源码解析
- EventBus3.0源码浅析与理解
- EventBus3.0以及老版本源码解析
- Android中EventBus3.0 源码解析
- Android EventBus3.0使用及源码解析
- Hibernate查询-不迫切左连接与迫切的区别
- centos6.5 ssh配置与使用
- java多线程_思维导图总结3
- thymeleaf 国际化
- SHELL笔记
- EventBus3 源码解析(个人理解) 之一
- 霍纳法则
- Lua入门教程 4.语句
- GridLayout 表格布局
- SQL 查找一个字段的首字母是以A~Z为首的数据
- PHP编程的50个细节
- 高德地图中缩放级别(zoom)和比例尺(getScalePerPixel)之间的计算关系
- 对立事件
- 多个模块编成一个模块