EventBus3 源码解析(个人理解) 之一

转载请说明地址: 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);
  1. 获取到这个类下的所有订阅方法集合。
  2. 如果该类没有订阅的方法,那么生成一个空的对象集合。
  3. 以这个类为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.为什么说直接发送到主线程不能运行太耗时的方法?

还有那个问题 我们开启下一篇去写吧?我想把细节什么都写清楚啦。

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页