美文网首页
EventBus源码分析(二)——粘性事件

EventBus源码分析(二)——粘性事件

作者: 小川君 | 来源:发表于2018-03-21 13:03 被阅读0次

    前言

    EventBus源码分析(一)中我们介绍了事件的注册、发送与反注册,本文的粘性事件是基于上篇文章,不同于一般事件,粘性事件主要在注册的时候有些不同的处理。

    一、事件注册

    粘性事件的注册方式是这样的:

    @Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
        public void pdmEventMainThread(Student student) {
            Log.e(TAG,"onEventMainThread: " + student.getContent());
        }
    

    主要是在订阅方法的注解里面加入了粘性标,设置sticky = true;
    我们看下注解类:

    public @interface Subscribe {
        ThreadMode threadMode() default ThreadMode.POSTING;
    
        /**
         * If true, delivers the most recent sticky event (posted with
         * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
         */
        boolean sticky() default false;
    
        /** Subscriber priority to influence the order of event delivery.
         * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
         * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
         * delivery among subscribers with different {@link ThreadMode}s! */
        int priority() default 0;
    }
    

    总共三个参数

    threadMode代表事件接收的线程,取值范围是POSTING 、MAIN 、BACKGROUND 、ASYNC

    • POSTING:事件的处理在和事件的发送在相同的线程,所以事件处理时间不应太长,不然影响事件的发送线程。
    • MAIN:事件的处理会在UI线程中执行,事件处理不应太长时间
    • BACKGROUND :事件的处理会在一个后台线程中执行,尽管是在后台线程中运行,事件处理时间不应太长。如果事件分发在主线程,件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。如果事件分发在后台线程,事件会立即执行处理
    • ASYNC :事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作
      threadMode 默认为POSTING,事件的处理与发送在相同的线程中

    sticky代表是否是粘性是事件 boolean类型

    默认为false

    priority代表事件的优先级

    默认为0 值越大 优先级越高

    在上篇文章中的第一小节,事件的注册中,有两个的地方需要注意下,这两个地方是对粘性事件的处理:

      /**
         * 解析订阅方法
         * @param findState
         */
        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;
            }
            for (Method method : methods) {
                int modifiers = method.getModifiers();
                //  解析方法类型
                if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    if (parameterTypes.length == 1) {
                        Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                        if (subscribeAnnotation != null) {
                            Class<?> eventType = parameterTypes[0];
                            // 第一次进入返回true  重复注册返回false
                            if (findState.checkAdd(method, eventType)) {
                                // 拿到注解类型里面的值
                                ThreadMode threadMode = subscribeAnnotation.threadMode();
                                findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                        subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                            }
                        }
                    } 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");
                }
            }
        }
    

    在这个解析订阅方法的方法里,我们拿到了订阅方法的注解类型以及对应的值,并保存到了findState对象中的subscriberMethods集合中中,并返回这个集合到register中

       public void register(Object subscriber) {
            Class<?> subscriberClass = subscriber.getClass();
            // 返回注册的订阅者中的订阅方法集
            List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
            synchronized (this) {
                // 遍历循环处理订阅者中的订阅方法
                for (SubscriberMethod subscriberMethod : subscriberMethods) {
                    subscribe(subscriber, subscriberMethod);
                }
            }
        }
    

    第二个要注意的地方就是在subscribe方法中:

       private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
           // 获取订阅方法的事件类型
            Class<?> eventType = subscriberMethod.eventType;
            Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
            /**
             * 通过订阅者中的订阅方法集 正常为情况下为null 如果多次注册 或者是在activity退出的时候没有反注册 就会报错
             * 并将订阅者与其中的订阅方法集储存起来
             */
            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);
                }
            }
    
            /**
             * 遍历将订阅方法添加进集合
             */
            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;
                }
            }
            /**
             * 获取订阅者中的订阅方法的入参类型对象  并保存起来
             */
            List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
            if (subscribedEvents == null) {
                subscribedEvents = new ArrayList<>();
                typesBySubscriber.put(subscriber, subscribedEvents);
            }
            subscribedEvents.add(eventType);
    
            if (subscriberMethod.sticky) {
                if (eventInheritance) {
                    // Existing sticky events of all subclasses of eventType have to be considered.
                    // Note: Iterating over all events may be inefficient with lots of sticky events,
                    // thus data structure should be changed to allow a more efficient lookup
                    // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                    Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                    for (Map.Entry<Class<?>, Object> entry : entries) {
                        Class<?> candidateEventType = entry.getKey();
                        if (eventType.isAssignableFrom(candidateEventType)) {
                            Object stickyEvent = entry.getValue();
                            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                        }
                    }
                } else {
                    Object stickyEvent = stickyEvents.get(eventType);
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        }
    

    回忆下上篇文章,这个方法主要是将订阅者中的订阅方法和事件类型进行分类储存

    • 以订阅者中的事件类型为key,该事件类型对应的该订阅者和订阅方法为value保存到subscriptionsByEventType中,
    • 以订阅者为key,该订阅者中的所有的事件类型的list集合为value保存到typesBySubscriber集合中。
      如果该事件是粘性事件,eventInheritance是由EventBuild赋值的,默认为true,具体作用没有我没有细究,不过在走完if方法体中的内容后,可以一起去看看else中的方法体。
      stickyEvents是在发送粘性事件的时候才赋值的
    // 发送事件
    EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!")); 
    //  事件的处理  并保存到stickyEvents
      public void postSticky(Object event) {
            synchronized (stickyEvents) {
                stickyEvents.put(event.getClass(), event);
            }
            // Should be posted after it is putted, in case the subscriber wants to remove immediately
            post(event);
        }
    

    所以在首次注册本事件类型的粘性事件时,这个if代码块是不会走的,所以也就有了
    可以先发送事件,而后再注册事件的情况 。 可以这么理解,事件只要发送了,无论是在发送前注册还是发送后注册,订阅者都能收到事件,这就是粘性事件。
    假设我们已经注册了 过了 去看看if代码块中的逻辑:
    for循环里面有个判断如果当前的事件类型与stickyEvents集合中的事件类是父>子关系的 那就执行checkPostStickyEventToSubscription方法。

        private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
            if (stickyEvent != null) {
                // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
                // --> Strange corner case, which we don't take care of here.
                postToSubscription(newSubscription, stickyEvent, 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 MAIN_ORDERED:
                    if (mainThreadPoster != null) {
                        mainThreadPoster.enqueue(subscription, event);
                    } else {
                        // temporary: technically not correct as poster not decoupled from subscriber
                        invokeSubscriber(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);
            }
        }
    

    后面就跟正常的事件发送一致了。
    我们再来看下else代码块中的逻辑,他是直接从stickyEvents获取 然后执行很简单
    这两个代码块的区别就是在for循环中的if判断语句,如果不是为if,那if代码块中的代码也完全可以写成else中的,我们结合下注释和isAssignableFrom 大致推测就是
    如果post(A),A extends B implements C,那么onEvent(A)、onEvent(B)、onEvent(C)会被调用
    具体的结果,还是由你们来校验吧。

    粘性事件的处理到这里基本就完了,下面就是EventBus提供的一些其他的关于粘性事件的接口

    获取某一个粘性事件,可以判断此粘性事件是否存在

     public <T> T getStickyEvent(Class<T> eventType) {
            synchronized (stickyEvents) {
                return eventType.cast(stickyEvents.get(eventType));
            }
        }
    

    删除某一个粘性事件(类类型的)

       public <T> T removeStickyEvent(Class<T> eventType) {
            synchronized (stickyEvents) {
                return eventType.cast(stickyEvents.remove(eventType));
            }
        }
    

    这个删除粘性事件是将stickyEvents集合中的粘性事件删除了 但是事件本身还是自带sticky = true属性,但是不影响

    删除某一个粘性事件(对象类型的)

       public boolean removeStickyEvent(Object event) {
            synchronized (stickyEvents) {
                Class<?> eventType = event.getClass();
                Object existingEvent = stickyEvents.get(eventType);
                if (event.equals(existingEvent)) {
                    stickyEvents.remove(eventType);
                    return true;
                } else {
                    return false;
                }
            }
        }
    

    两个都是一样的,只是入参不一样
    删除所有粘性事件 很强

     public void removeAllStickyEvents() {
            synchronized (stickyEvents) {
                stickyEvents.clear();
            }
        }
    

    相关文章

      网友评论

          本文标题:EventBus源码分析(二)——粘性事件

          本文链接:https://www.haomeiwen.com/subject/cwbwqftx.html