初试EventBus

作者: xiasuhuei321 | 来源:发表于2017-06-05 21:33 被阅读0次

    写在前面

    EventBus一直都有听说过,但是之前都没有用过。因为自己的项目里并没有特别复杂的事件传递,自己写个接口回调弄一下解决需求就ok了。最近有需求要了解一下EventBus,将自己的所得记录一下。

    基本使用

    首先在app的build.gradle中添加引用代码:

    compile 'org.greenrobot:eventbus:3.0.0'
    

    这样就可以在项目中使用EventBus了。

    这里简单的测试一下post普通的事件和粘性事件,简单的介绍一下我所使用的测试例,有两个Activity,MainActivity和SecondActivity。使用步骤如下:

    • register & unregister

    官方的介绍中是在Activity的onStart和onStop这两个生命周期中调用了register和unregister,这个根据你自己的需求。如果你与需要在Activity不可见时也要能接收到事件,那么无疑在onDestroy中unregister更好。

        @Override
        protected void onStart() {
            super.onStart();
            EventBus.getDefault().register(this);
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            EventBus.getDefault().unregister(this);
        }
    

    接着定义要传递的事件,这里只是简单的测试一下,所以只简单的复写了一下toString方法

        public static class MessageEvent {
            @Override
            public String toString() {
                return "onMessageEvent";
            }
        }
    

    由于SecondActivity在MainActivity之后启动,所以需要poststicky(粘性)事件:

    EventBus.getDefault().postSticky(new MessageEvent());
    

    在SecondActivity中使用@Subscribe注解标注一个方法,参数为刚才定义的事件:

        @Subscribe
        public void onMessageEvent(MainActivity.MessageEvent e) {
            Log.e(TAG, e.toString());
        }
    

    整个EventBus的使用流程就是这样了,当然,不要急着去试,你直接用上面的代码肯定是不行的,为什么呢?可以看一下@Subscribe这个注解的源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    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;
    }
    

    可以看到有个sticky()方法默认返回值是false,而上面的注释也说了为真的时候会分发sticky event。有一点也要注意,这个注解上面的@Retention(RetentionPolicy.RUNTIME)注解说明这个Subscribe注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。如果你足够了解反射和注解,你就能想到EventBus肯定是利用反射来调用注解方法的。

    说了这么多,正确的代码是怎样的呢?

        @Subscribe(sticky = true)
        public void onMessageEvent(MainActivity.MessageEvent e) {
            Log.e(TAG, e.toString());
        }
    

    输出:

    输出

    可以看到的确是接收到了事件。基本的使用方式就是这样。

    Android需要在主线程更新UI,那么EventBus如何切换线程?

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessage(MessageEvent event) {
        textField.setText(event.message);
    }
    

    在注解中声明threadMode = ThreadMode.Main就可以了,这样你就可以在接收到事件的时候更新UI了。ThreadMode还有POSTING、BACKGROUND和ASYNC这几个值。POSTING是默认值,就是不切换线程,直接发送事件。

    更多关于EventBus的使用可以参考官方文档:http://greenrobot.org/eventbus/documentation/

    EventBus是如何工作的?

    EventBus使用起来是比较简单的,刚在上面也说了EventBus通过注解配合反射来实现传递事件的功能,接下来就简要分析一下post()的流程。EventBus中的post方法里首先做了一些简单的操作和判断,接着调用了postSingleEvent()方法:

        private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
            Class<?> eventClass = event.getClass();
            boolean subscriptionFound = false;
            if (eventInheritance) {
                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 {
                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) {
                    post(new NoSubscriberEvent(this, event));
                }
            }
        }
    

    可以看到首先拿到了event的Class对象,接着判断eventInheritance值,这个值默认是true,那么接着就调用了lookupAllEventTypes方法,这个方法不具体分析代码,他的功能是将传入event的所有父类或者接口的Class对象添加到一个集合中。他这样做的原因我认为是考虑到了多态问题,用户在用的时候可能会用父类Event作为注解方法的参数,而post的时候则post子类或者接口实现类之类的东西。接着遍历拿到的Class对象集合,调用postSingleEventForEventType方法。

        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;
        }
    

    首先创建了一个线程安全的集合,线程安全啥的这里不展开讨论,加锁的代码中subscriptionsByEventType究竟是个什么东西呢?

    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    

    光看这个并没有了解到什么,然而这是个map,只要搜一下subscriptionsByEventType.put就可以看到赋值的操作了。经过搜索,在代码中发现啊subscribe这个方法中有调用。经过进一步的搜索,发现在register方法中调用了subscribe方法。是的,我上面讲post流程直接跳过了register这个流程,现在得补回来了。

    register方法中调用了SubscriberMethod的findSubscriberMethods方法,具体的实现逻辑在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;
            }
            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];
                            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");
                }
            }
        }
    

    这里可以很清晰的看到就是利用了反射来拿到注解的方法。简单的了解到如何拿到注解方法,继续回到postSingleEventForEventType方法,方法遍历了subscriptions,并将subscriptions集合的元素、event和是否是主线程的布尔值作为参数传递给postToSubscription方法:

        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);
            }
        }
    

    这里可以看到对不同的threadMode进行了不同的处理,最终通过反射调用了注解方法。整个流程到此就简单的过了一遍,EventBus是通过反射来工作的,不过使用的是编译时注解。

    这里只是简单的过了一下流程,中间跳过了很多代码,也没有很深入的分析源码。EventBus的源码并不算特别多,如果各位对反射和注解比较了解,可以读一下EventBus的源码。

    相关文章

      网友评论

        本文标题:初试EventBus

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