LJ的Blog

学海无涯苦做舟

0%

初试EventBus

写在前面

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

基本使用

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

1
compile 'org.greenrobot:eventbus:3.0.0'

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

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

  • register & unregister

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

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}

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

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

1
2
3
4
5
6
public static class MessageEvent {
@Override
public String toString() {
return "onMessageEvent";
}
}

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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肯定是利用反射来调用注解方法的。

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

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

输出:

输出

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

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

1
2
3
4
@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()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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究竟是个什么东西呢?

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

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

register方法中调用了SubscriberMethod的findSubscriberMethods方法,具体的实现逻辑在findUsingReflectionInSingleClass方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    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方法:
```java
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的源码。