初试EventBus

写在前面

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是通过反射来工作的,不同于dagger2用的是编译时注解,EventBus用的是Java本身提供的注解和反射,对性能会有影响,不过比起其易用方便的特性来说,还是值得用的。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值