[关闭]
@zouzhenglu 2018-02-01T13:59:04.000000Z 字数 10842 阅读 106

EventBus3.0详解

android EventBus


修改日志
2017-12-1 添加索引部分得细节,添加kotlin的支持方式

写在前面

1.前言

曾经,一层又一层的业务逻辑让我不知所措,一个又一个的回调让你头晕眼花,一个又一个的参数让你混乱不堪。EventBus,,一个耦合度低到令你害怕的框架。

2.什么是EventBus

EventBus是一个消息总线,以观察者模式实现,用于简化程序的组件、线程通信,可以轻易切换线程、开辟线程。EventBus3.0跟先前版本的区别在于加入了annotation @Subscribe,取代了以前约定命名的方式。

3.相似产品比较

产品名 开发者 备注
EventBus greenrobot 用户最多,简洁,方便,小巧,文档简洁明了
Guava google 一个庞大的工具类库,EventBus只是一个小功能
otto square fork guava ,用的人不少
AndroidEventBus 何红辉 模仿EventBus开发的

使用EventBus3.0三部曲

1.定义事件

  1. public class MessageEvent {
  2. public final String message;
  3. public MessageEvent(String message) {
  4. this.message = message;
  5. }
  6. }

2.准备订阅者

  1. // This method will be called when a MessageEvent is posted
  2. @Subscribe
  3. public void onMessageEvent(MessageEvent event){
  4. Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
  5. }
  6. // This method will be called when a SomeOtherEvent is posted
  7. @Subscribe
  8. public void handleSomethingElse(SomeOtherEvent event){
  9. doSomethingWith(event);
  10. }
  1. @Override
  2. public void onStart() {
  3. super.onStart();
  4. EventBus.getDefault().register(this);
  5. }
  6. @Override
  7. public void onStop() {
  8. EventBus.getDefault().unregister(this);
  9. super.onStop();
  10. }

3.发送事件

  1. EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

深入了解

1.ThreadMode线程通信

EventBus可以很简单的实现线程间的切换,包括后台线程、UI线程、异步线程

ThreadMode.POSTING

  1. //默认调用方式,在调用post方法的线程执行,避免了线程切换,性能开销最少
  2. // Called in the same thread (default)
  3. @Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here
  4. public void onMessage(MessageEvent event) {
  5. log(event.message);
  6. }

ThreadMode.MAIN

  1. // Called in Android UI's main thread
  2. @Subscribe(threadMode = ThreadMode.MAIN)
  3. public void onMessage(MessageEvent event) {
  4. textField.setText(event.message);
  5. }

ThreadMode.BACKGROUND

  1. // 如果调用post方法的线程不是主线程,则直接在该线程执行
  2. // 如果是主线程,则切换到后台单例线程,多个方法公用同个后台线程,按顺序执行,避免耗时操作
  3. // Called in the background thread
  4. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  5. public void onMessage(MessageEvent event){
  6. saveToDisk(event.message);
  7. }

ThreadMode.ASYNC

  1. //开辟新独立线程,用来执行耗时操作,例如网络访问
  2. //EventBus内部使用了线程池,但是要尽量避免大量长时间运行的异步线程,限制并发线程数量
  3. //可以通过EventBusBuilder修改,默认使用Executors.newCachedThreadPool()
  4. // Called in a separate thread
  5. @Subscribe(threadMode = ThreadMode.ASYNC)
  6. public void onMessage(MessageEvent event){
  7. backend.send(event.message);
  8. }

2.配置EventBusBuilder

EventBus提供了很多配置,一般的情况下我们可以不用配置.但是,如果你有一些其他要求,比如控制日志在开发的时候输出,发布的时候不输出,在开发的时候错误崩溃,而发布的时候不崩溃...等情况。
EventBus提供了一个默认的实现,但不是单例。

  1. EventBus eventBus = new EventBus();
  2. //下面这一条的效果是完全一样的
  3. EventBus eventBus = EventBus.builder().build();
  4. //修改默认实现的配置,记住,必须在第一次EventBus.getDefault()之前配置,且只能设置一次。建议在application.onCreate()调用
  5. EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();

3.StickyEvent

StickyEvent在内存中保存最新的消息,取消原有消息,执行最新消息,只有在注册后才会执行,如果没有注册,消息会一直保留来内存中

  1. //在注册之前发送消息
  2. EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
  1. //限制,新界面启动了
  2. @Override
  3. public void onStart() {
  4. super.onStart();
  5. EventBus.getDefault().register(this);
  6. }
  7. //在onStart调用register后,执行消息
  8. @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
  9. public void onEvent(MessageEvent event) {
  10. // UI updates must run on MainThread
  11. textField.setText(event.message);
  12. }
  13. @Override
  14. public void onStop() {
  15. EventBus.getDefault().unregister(this);
  16. super.onStop();
  17. }

你也可以手动管理StickyEvent

  1. MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
  2. // Better check that an event was actually posted before
  3. if(stickyEvent != null) {
  4. // "Consume" the sticky event
  5. EventBus.getDefault().removeStickyEvent(stickyEvent);
  6. //or
  7. EventBus.getDefault().removeAllStickyEvents();
  8. // Now do something with it
  9. }

在这里,或许你会有个疑问,
StickyEvent=true的订阅者能否接收post的事件?
StickyEvent=false的订阅者能否接收postSticky的事件?
查看源码发现

  1. /**
  2. * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
  3. * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
  4. */
  5. public void postSticky(Object event) {
  6. synchronized (stickyEvents) {
  7. stickyEvents.put(event.getClass(), event);
  8. }
  9. // Should be posted after it is putted, in case the subscriber wants to remove immediately
  10. post(event);
  11. }
  1. private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
  2. ...省略部分代码
  3. if (subscriberMethod.sticky) {
  4. if (eventInheritance) {
  5. // Existing sticky events of all subclasses of eventType have to be considered.
  6. // Note: Iterating over all events may be inefficient with lots of sticky events,
  7. // thus data structure should be changed to allow a more efficient lookup
  8. // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
  9. Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
  10. for (Map.Entry<Class<?>, Object> entry : entries) {
  11. Class<?> candidateEventType = entry.getKey();
  12. if (eventType.isAssignableFrom(candidateEventType)) {
  13. Object stickyEvent = entry.getValue();
  14. checkPostStickyEventToSubscription(newSubscription, stickyEvent);
  15. }
  16. }
  17. } else {
  18. Object stickyEvent = stickyEvents.get(eventType);
  19. checkPostStickyEventToSubscription(newSubscription, stickyEvent);
  20. }
  21. }
  22. }
  1. boolean checkAdd(Method method, Class<?> eventType) {
  2. // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
  3. // Usually a subscriber doesn't have methods listening to the same event type.
  4. Object existing = anyMethodByEventType.put(eventType, method);
  5. if (existing == null) {
  6. return true;
  7. } else {
  8. if (existing instanceof Method) {
  9. if (!checkAddWithMethodSignature((Method) existing, eventType)) {
  10. // Paranoia check
  11. throw new IllegalStateException();
  12. }
  13. // Put any non-Method object to "consume" the existing Method
  14. anyMethodByEventType.put(eventType, this);
  15. }
  16. return checkAddWithMethodSignature(method, eventType);
  17. }
  18. }

发现,post方法没有过滤StickyEvent,而postSticky是调用post方法的,所以,无论post还是postSticky,StickyEvent是true或false,都会执行

4.priority事件优先级

  1. //priority越大,级别越高
  2. @Subscribe(priority = 1);
  3. public void onEvent(MessageEvent event) {
  4. }
  1. //优先级实现方式,遍历当前列表,把当前
  2. int size = subscriptions.size();
  3. for (int i = 0; i <= size; i++) {
  4. if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
  5. subscriptions.add(i, newSubscription);
  6. break;
  7. }
  8. }

5.中止事件传递

  1. // 中止事件传递,后续事件不在调用,注意,只能在传递事件的时候调用
  2. @Subscribe
  3. public void onEvent(MessageEvent event){
  4. EventBus.getDefault().cancelEventDelivery(event) ;
  5. }

6.index索引加速

EventBus使用了annotation,默认在编译时生成代码,生成索引,
添加index后会在编译时运行,自动生成相应代码。
ps:由于apt的限制,匿名内部类中的annotation不会被识别,会自动降级在运行时反射,此时,效率会降低
EventBus官网地址

  1. buildscript {
  2. dependencies {
  3. classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  4. }
  5. }
  6. apply plugin: 'com.neenbedankt.android-apt'
  7. dependencies {
  8. compile 'org.greenrobot:eventbus:3.0.0'
  9. apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
  10. }
  11. apt {
  12. arguments {
  13. eventBusIndex "com.example.myapp.MyEventBusIndex"
  14. }
  15. }

由于kotlin的使用annotation处理方式不同,需要使用kapt

  1. apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
  2. dependencies {
  3. compile 'org.greenrobot:eventbus:3.1.1'
  4. kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
  5. }
  6. kapt {
  7. arguments {
  8. arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
  9. }
  10. }

ps:如果是多个模块都需要加索引,则每个模块都要加上上面三个apt,包引入可以不需要重复

这样,编译后就会自动生成代码

  1. /** This class is generated by EventBus, do not edit. */
  2. public class MyEventBusIndex implements SubscriberInfoIndex {
  3. private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
  4. static {
  5. SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
  6. putIndex(new SimpleSubscriberInfo(com.zhiyecn.demoeventbus.ScrollingActivity.class, true,
  7. new SubscriberMethodInfo[] {
  8. new SubscriberMethodInfo("eventBus", com.zhiyecn.demoeventbus.StickEvent.class),
  9. new SubscriberMethodInfo("eventBusStick", com.zhiyecn.demoeventbus.StickEvent.class, ThreadMode.BACKGROUND,
  10. 0, true),
  11. }));
  12. }
  13. private static void putIndex(SubscriberInfo info) {
  14. SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
  15. }
  16. @Override
  17. public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
  18. SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
  19. if (info != null) {
  20. return info;
  21. } else {
  22. return null;
  23. }
  24. }
  25. }

在编译时通过反射生成event方法存在map中,在发送事件的时候,调用getSubscriberInfo获取该map。

  1. private SubscriberInfo getSubscriberInfo(FindState findState) {
  2. ...省略
  3. if (subscriberInfoIndexes != null) {
  4. for (SubscriberInfoIndex index : subscriberInfoIndexes) {
  5. SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
  6. if (info != null) {
  7. return info;
  8. }
  9. }
  10. }
  11. return null;
  12. }

使用方式

  1. //给自定义eventbus添加索引
  2. EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
  1. //给默认eventbus添加索引
  2. EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
  3. // Now the default instance uses the given index. Use it like this:
  4. EventBus eventBus = EventBus.getDefault();
  1. //多个库需要添加索引
  2. EventBus eventBus = EventBus.builder()
  3. .addIndex(new MyEventBusAppIndex())
  4. .addIndex(new MyEventBusLibIndex()).build();

7.NoSubscriberEvent

如果没找到订阅者事件,可以通过EventBusBuilder设置是否默认发送NoSubscriberEvent,默认是打开的

  1. /**
  2. * This Event is posted by EventBus when no subscriber is found for a posted event.
  3. *
  4. * @author Markus
  5. */
  6. public final class NoSubscriberEvent {
  7. /** The {@link EventBus} instance to with the original event was posted to. */
  8. public final EventBus eventBus;
  9. /** The original event that could not be delivered to any subscriber. */
  10. public final Object originalEvent;
  11. public NoSubscriberEvent(EventBus eventBus, Object originalEvent) {
  12. this.eventBus = eventBus;
  13. this.originalEvent = originalEvent;
  14. }
  15. }
  1. private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
  2. ....
  3. if (!subscriptionFound) {
  4. if (logNoSubscriberMessages) {
  5. Log.d(TAG, "No subscribers registered for event " + eventClass);
  6. }
  7. if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
  8. eventClass != SubscriberExceptionEvent.class) {
  9. post(new NoSubscriberEvent(this, event));
  10. }
  11. }
  12. }

8.混淆

  1. -keepattributes *Annotation*
  2. -keepclassmembers class ** {
  3. @org.greenrobot.eventbus.Subscribe <methods>;
  4. }
  5. -keep enum org.greenrobot.eventbus.ThreadMode { *; }
  6. # Only required if you use AsyncExecutor
  7. -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
  8. <init>(java.lang.Throwable);
  9. }

9.利弊

好处

简单,方便,小巧,文档清晰,性能消耗少,可定制行强,耦合度低

坏处

耦合度太低

这绝对不是个笑话,,EventBus的耦合太低了,如果不加以控制管理,你会不知道,你发的消息到跑哪里去了。也不知道你的这条消息,会在哪里发出。如果你没有很好的方法解决这个问题,建议不好用太多。

使用建议

1、EventBus管理

EventBus运行创建多个,那么,明确事件的生命周期,根据不同生命周期使用不同的EventBus?

  1. /**
  2. * 方法1
  3. * 用annotation配合使用工厂
  4. * EventBusFactory.getBus(EventBusFactory.START);
  5. * EventBusFactory.getBus();
  6. */
  7. public class EventBusFactory {
  8. private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2);
  9. @IntDef({CREATE, START})
  10. @Retention(RetentionPolicy.SOURCE)
  11. public @interface BusType {
  12. }
  13. public static final int CREATE = 0;
  14. public static final int START = 1;
  15. static {
  16. mBusSparseArray.put(CREATE, EventBus.builder().build());
  17. mBusSparseArray.put(START, EventBus.getDefault());
  18. }
  19. public static EventBus getBus() {
  20. return getBus(START);
  21. }
  22. public static EventBus getBus(@BusType int type) {
  23. return mBusSparseArray.get(type);
  24. }
  25. }
  1. /**
  2. * 方法2
  3. * 用枚举工厂
  4. * EventBusFactory.START.getBus();
  5. */
  6. public enum EventBusFactory {
  7. CREATE(0),
  8. START(1);
  9. private int mType;
  10. EventBusFactory(int type) {
  11. mType = type;
  12. }
  13. public EventBus getBus() {
  14. return mBusSparseArray.get(mType);
  15. }
  16. private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2);
  17. static {
  18. mBusSparseArray.put(CREATE.mType, EventBus.builder().build());
  19. mBusSparseArray.put(START.mType, EventBus.getDefault());
  20. }
  21. }

2、以事件为对象

将数据封装到一个事件类。所有事件放到一个包下。如果事件太多,同个模块的事件可以考虑使用静态内部类,或者再分包。

  1. public class Event {
  2. public static class UserListEvent {
  3. public List<User> users ;
  4. }
  5. public static class ItemListEvent {
  6. public List<Item> items;
  7. }
  8. }

注意,不是相同类型就一定要作为一个事件封装,具体需要考虑业务情景跟代码情况,比如事件行为不同、事件生命周期不同,如果有必要,写封装成两个Event可能是更好的选择。

  1. public class Event {
  2. public static class UserListUpdateEventOnCreate {
  3. public List<User> users;
  4. }
  5. public static class UserListUpdateEventOnStart {
  6. public List<User> users ;
  7. }
  8. public static class UserListRemoveEventOnStart {
  9. public List<User> users;
  10. }
  11. }

参考文献

  1. EventBus官网地址
  2. EventBus github地址
  3. EventBus 3.0的用法详解

说了这么多废话,,下面进入正题

福利

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注