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);
  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.为什么说直接发送到主线程不能运行太耗时的方法?

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

0 0