@zouzhenglu
2018-02-01T13:59:04.000000Z
字数 10842
阅读 156
android EventBus
修改日志
2017-12-1 添加索引部分得细节,添加kotlin的支持方式
曾经,一层又一层的业务逻辑让我不知所措,一个又一个的回调让你头晕眼花,一个又一个的参数让你混乱不堪。EventBus,,一个耦合度低到令你害怕的框架。
EventBus是一个消息总线,以观察者模式实现,用于简化程序的组件、线程通信,可以轻易切换线程、开辟线程。EventBus3.0跟先前版本的区别在于加入了annotation @Subscribe,取代了以前约定命名的方式。
| 产品名 | 开发者 | 备注 |
|---|---|---|
| EventBus | greenrobot | 用户最多,简洁,方便,小巧,文档简洁明了 |
| Guava | 一个庞大的工具类库,EventBus只是一个小功能 | |
| otto | square | fork guava ,用的人不少 |
| AndroidEventBus | 何红辉 | 模仿EventBus开发的 |
public class MessageEvent {public final String message;public MessageEvent(String message) {this.message = message;}}
// This method will be called when a MessageEvent is posted@Subscribepublic void onMessageEvent(MessageEvent event){Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();}// This method will be called when a SomeOtherEvent is posted@Subscribepublic void handleSomethingElse(SomeOtherEvent event){doSomethingWith(event);}
@Overridepublic void onStart() {super.onStart();EventBus.getDefault().register(this);}@Overridepublic void onStop() {EventBus.getDefault().unregister(this);super.onStop();}
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
EventBus可以很简单的实现线程间的切换,包括后台线程、UI线程、异步线程
//默认调用方式,在调用post方法的线程执行,避免了线程切换,性能开销最少// Called in the same thread (default)@Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional herepublic void onMessage(MessageEvent event) {log(event.message);}
// Called in Android UI's main thread@Subscribe(threadMode = ThreadMode.MAIN)public void onMessage(MessageEvent event) {textField.setText(event.message);}
// 如果调用post方法的线程不是主线程,则直接在该线程执行// 如果是主线程,则切换到后台单例线程,多个方法公用同个后台线程,按顺序执行,避免耗时操作// Called in the background thread@Subscribe(threadMode = ThreadMode.BACKGROUND)public void onMessage(MessageEvent event){saveToDisk(event.message);}
//开辟新独立线程,用来执行耗时操作,例如网络访问//EventBus内部使用了线程池,但是要尽量避免大量长时间运行的异步线程,限制并发线程数量//可以通过EventBusBuilder修改,默认使用Executors.newCachedThreadPool()// Called in a separate thread@Subscribe(threadMode = ThreadMode.ASYNC)public void onMessage(MessageEvent event){backend.send(event.message);}
EventBus提供了很多配置,一般的情况下我们可以不用配置.但是,如果你有一些其他要求,比如控制日志在开发的时候输出,发布的时候不输出,在开发的时候错误崩溃,而发布的时候不崩溃...等情况。
EventBus提供了一个默认的实现,但不是单例。
EventBus eventBus = new EventBus();//下面这一条的效果是完全一样的EventBus eventBus = EventBus.builder().build();//修改默认实现的配置,记住,必须在第一次EventBus.getDefault()之前配置,且只能设置一次。建议在application.onCreate()调用EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();
StickyEvent在内存中保存最新的消息,取消原有消息,执行最新消息,只有在注册后才会执行,如果没有注册,消息会一直保留来内存中
//在注册之前发送消息EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
//限制,新界面启动了@Overridepublic 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 MainThreadtextField.setText(event.message);}@Overridepublic void onStop() {EventBus.getDefault().unregister(this);super.onStop();}
你也可以手动管理StickyEvent
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);// Better check that an event was actually posted beforeif(stickyEvent != null) {// "Consume" the sticky eventEventBus.getDefault().removeStickyEvent(stickyEvent);//orEventBus.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 immediatelypost(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 checkthrow new IllegalStateException();}// Put any non-Method object to "consume" the existing MethodanyMethodByEventType.put(eventType, this);}return checkAddWithMethodSignature(method, eventType);}}
发现,post方法没有过滤StickyEvent,而postSticky是调用post方法的,所以,无论post还是postSticky,StickyEvent是true或false,都会执行
//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;}}
// 中止事件传递,后续事件不在调用,注意,只能在传递事件的时候调用@Subscribepublic void onEvent(MessageEvent event){…EventBus.getDefault().cancelEventDelivery(event) ;}
EventBus使用了annotation,默认在编译时生成代码,生成索引,
添加index后会在编译时运行,自动生成相应代码。
ps:由于apt的限制,匿名内部类中的annotation不会被识别,会自动降级在运行时反射,此时,效率会降低
EventBus官网地址
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"}}
由于kotlin的使用annotation处理方式不同,需要使用kapt
apply plugin: 'kotlin-kapt' // ensure kapt plugin is applieddependencies {compile 'org.greenrobot:eventbus:3.1.1'kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'}kapt {arguments {arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')}}
ps:如果是多个模块都需要加索引,则每个模块都要加上上面三个apt,包引入可以不需要重复
这样,编译后就会自动生成代码
/** This class is generated by EventBus, do not edit. */public class MyEventBusIndex implements SubscriberInfoIndex {private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;static {SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();putIndex(new SimpleSubscriberInfo(com.zhiyecn.demoeventbus.ScrollingActivity.class, true,new SubscriberMethodInfo[] {new SubscriberMethodInfo("eventBus", com.zhiyecn.demoeventbus.StickEvent.class),new SubscriberMethodInfo("eventBusStick", com.zhiyecn.demoeventbus.StickEvent.class, ThreadMode.BACKGROUND,0, true),}));}private static void putIndex(SubscriberInfo info) {SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);}@Overridepublic SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);if (info != null) {return info;} else {return null;}}}
在编译时通过反射生成event方法存在map中,在发送事件的时候,调用getSubscriberInfo获取该map。
private SubscriberInfo getSubscriberInfo(FindState findState) {...省略if (subscriberInfoIndexes != null) {for (SubscriberInfoIndex index : subscriberInfoIndexes) {SubscriberInfo info = index.getSubscriberInfo(findState.clazz);if (info != null) {return info;}}}return null;}
使用方式
//给自定义eventbus添加索引EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
//给默认eventbus添加索引EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();// Now the default instance uses the given index. Use it like this:EventBus eventBus = EventBus.getDefault();
//多个库需要添加索引EventBus eventBus = EventBus.builder().addIndex(new MyEventBusAppIndex()).addIndex(new MyEventBusLibIndex()).build();
如果没找到订阅者事件,可以通过EventBusBuilder设置是否默认发送NoSubscriberEvent,默认是打开的
/*** 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;}}
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));}}}
-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);}
好处
简单,方便,小巧,文档清晰,性能消耗少,可定制行强,耦合度低
坏处
耦合度太低
这绝对不是个笑话,,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、以事件为对象
将数据封装到一个事件类。所有事件放到一个包下。如果事件太多,同个模块的事件可以考虑使用静态内部类,或者再分包。
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;}}