EventBus3.0详解

来源:互联网 发布:vb怎么建立数据库 编辑:程序博客网 时间:2024/05/19 12:35

1.前言

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

2.什么是EventBus

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

3.相似产品比较

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

使用EventBus3.0三部曲

1.定义事件

public class MessageEvent {    public final String message;    public MessageEvent(String message) {        this.message = message;    }}

2.准备订阅者

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

3.发送事件

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

深入了解

1.ThreadMode线程通信

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

ThreadMode.POSTING

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

ThreadMode.MAIN

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

ThreadMode.BACKGROUND

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

ThreadMode.ASYNC

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

2.配置EventBusBuilder

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

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

3.StickyEvent

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

    //在注册之前发送消息    EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
    //限制,新界面启动了   @Override    public void onStart() {        super.onStart();        EventBus.getDefault().register(this);    }    //在onStart调用register后,执行消息    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)    public void onEvent(MessageEvent event) {        // UI updates must run on MainThread        textField.setText(event.message);    }    @Override    public void onStop() {        EventBus.getDefault().unregister(this);        super.onStop();    }

你也可以手动管理StickyEvent

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

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

   /**     * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky     * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.     */    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);    }
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {        ...省略部分代码        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);            }        }    }
        boolean checkAdd(Method method, Class<?> eventType) {            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.            // Usually a subscriber doesn't have methods listening to the same event type.            Object existing = anyMethodByEventType.put(eventType, method);            if (existing == null) {                return true;            } else {                if (existing instanceof Method) {                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {                        // Paranoia check                        throw new IllegalStateException();                    }                    // Put any non-Method object to "consume" the existing Method                    anyMethodByEventType.put(eventType, this);                }                return checkAddWithMethodSignature(method, eventType);            }        }

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

4.priority事件优先级

    //priority越大,级别越高    @Subscribe(priority = 1);    public void onEvent(MessageEvent event) {    …    }
//优先级实现方式,遍历当前列表,把当前    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;        }    }

5.中止事件传递

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

6.index索引加速

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

buildscript {    dependencies {        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'    }}apply plugin: 'com.neenbedankt.android-apt'dependencies {    compile 'org.greenrobot:eventbus:3.0.0'    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'}apt {    arguments {        eventBusIndex "com.example.myapp.MyEventBusIndex"    }}
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();// Now the default instance uses the given index. Use it like this:EventBus eventBus = EventBus.getDefault();

7.NoSubscriberEvent

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

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {        ....        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));            }        }    }

8.混淆

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

9.利弊

好处

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

坏处

耦合度太低

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

使用建议

1、EventBus管理

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

/** * 方法1 * 用annotation配合使用工厂 * EventBusFactory.getBus(EventBusFactory.START); * EventBusFactory.getBus(); */public class EventBusFactory {    private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2);    @IntDef({CREATE, START})    @Retention(RetentionPolicy.SOURCE)    public @interface BusType {    }    public static final int CREATE = 0;    public static final int START = 1;    static {        mBusSparseArray.put(CREATE, EventBus.builder().build());        mBusSparseArray.put(START, EventBus.getDefault());    }    public static EventBus getBus() {        return getBus(START);    }    public static EventBus getBus(@BusType int type) {        return mBusSparseArray.get(type);    }}
/** * 方法2 * 用枚举工厂 * EventBusFactory.START.getBus(); */public enum EventBusFactory {    CREATE(0),    START(1);    private int mType;    EventBusFactory(int type) {        mType = type;    }    public EventBus getBus() {        return mBusSparseArray.get(mType);    }    private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2);    static {        mBusSparseArray.put(CREATE.mType, EventBus.builder().build());        mBusSparseArray.put(START.mType, EventBus.getDefault());    }}

2、以事件为对象

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

/** * This Event is posted by EventBus when no subscriber is found for a posted event. *  * @author Markus */public final class NoSubscriberEvent {    /** The {@link EventBus} instance to with the original event was posted to. */    public final EventBus eventBus;    /** The original event that could not be delivered to any subscriber. */    public final Object originalEvent;    public NoSubscriberEvent(EventBus eventBus, Object originalEvent) {        this.eventBus = eventBus;        this.originalEvent = originalEvent;    }}
public class Event  {      public static class UserListEvent {          public List<User> users ;      }    public static class ItemListEvent {          public List<Item> items;      }    }

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

public class Event  {     public static class UserListUpdateEventOnCreate {          public List<User> users;      }     public static class UserListUpdateEventOnStart {          public List<User> users ;      }    public static class UserListRemoveEventOnStart {          public List<User> users;      } }

参考文献

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


作者:zouzhenglu
链接:http://www.jianshu.com/p/31e3528ca7e5
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原创粉丝点击