android EventBus
修改日志
2017-12-1 添加索引部分得細(xì)節(jié)态蒂,添加kotlin的支持方式
寫在前面
1.前言
曾經(jīng),一層又一層的業(yè)務(wù)邏輯讓我不知所措辽社,一個(gè)又一個(gè)的回調(diào)讓你頭暈眼花伟墙,一個(gè)又一個(gè)的參數(shù)讓你混亂不堪。EventBus滴铅,戳葵,一個(gè)耦合度低到令你害怕的框架。
2.什么是EventBus
EventBus是一個(gè)消息總線汉匙,以觀察者模式實(shí)現(xiàn)拱烁,用于簡(jiǎn)化程序的組件、線程通信噩翠,可以輕易切換線程戏自、開辟線程。EventBus3.0跟先前版本的區(qū)別在于加入了annotation @Subscribe伤锚,取代了以前約定命名的方式擅笔。
3.相似產(chǎn)品比較
產(chǎn)品名 | 開發(fā)者 | 備注 |
---|---|---|
EventBus | greenrobot | 用戶最多,簡(jiǎn)潔见芹,方便剂娄,小巧玄呛,文檔簡(jiǎn)潔明了 |
Guava | 一個(gè)龐大的工具類庫,EventBus只是一個(gè)小功能 | |
otto | square | fork guava 徘铝,用的人不少 |
AndroidEventBus | 何紅輝 | 模仿EventBus開發(fā)的 |
使用EventBus3.0三部曲
1.定義事件
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
2.準(zhǔn)備訂閱者
// 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.發(fā)送事件
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
深入了解
1.ThreadMode線程通信
EventBus可以很簡(jiǎn)單的實(shí)現(xiàn)線程間的切換,包括后臺(tái)線程惕它、UI線程、異步線程
ThreadMode.POSTING
//默認(rèn)調(diào)用方式郁惜,在調(diào)用post方法的線程執(zhí)行,避免了線程切換甲锡,性能開銷最少
// 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
// 如果調(diào)用post方法的線程不是主線程羽戒,則直接在該線程執(zhí)行
// 如果是主線程易稠,則切換到后臺(tái)單例線程驶社,多個(gè)方法公用同個(gè)后臺(tái)線程,按順序執(zhí)行硅瞧,避免耗時(shí)操作
// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
ThreadMode.ASYNC
//開辟新獨(dú)立線程,用來執(zhí)行耗時(shí)操作,例如網(wǎng)絡(luò)訪問
//EventBus內(nèi)部使用了線程池,但是要盡量避免大量長(zhǎng)時(shí)間運(yùn)行的異步線程月腋,限制并發(fā)線程數(shù)量
//可以通過EventBusBuilder修改瓣赂,默認(rèn)使用Executors.newCachedThreadPool()
// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
2.配置EventBusBuilder
EventBus提供了很多配置煌集,一般的情況下我們可以不用配置.但是,如果你有一些其他要求,比如控制日志在開發(fā)的時(shí)候輸出,發(fā)布的時(shí)候不輸出,在開發(fā)的時(shí)候錯(cuò)誤崩潰卷拘,而發(fā)布的時(shí)候不崩潰...等情況。
EventBus提供了一個(gè)默認(rèn)的實(shí)現(xiàn)乍赫,但不是單例揪惦。
EventBus eventBus = new EventBus();
//下面這一條的效果是完全一樣的
EventBus eventBus = EventBus.builder().build();
//修改默認(rèn)實(shí)現(xiàn)的配置器腋,記住纫塌,必須在第一次EventBus.getDefault()之前配置措左,且只能設(shè)置一次。建議在application.onCreate()調(diào)用
EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();
3.StickyEvent
StickyEvent在內(nèi)存中保存最新的消息凉逛,取消原有消息,執(zhí)行最新消息诬辈,只有在注冊(cè)后才會(huì)執(zhí)行口渔,如果沒有注冊(cè),消息會(huì)一直保留來內(nèi)存中
//在注冊(cè)之前發(fā)送消息
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
//限制枪向,新界面啟動(dòng)了
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
//在onStart調(diào)用register后,執(zhí)行消息
@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();
}
你也可以手動(dòng)管理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
}
在這里负蠕,或許你會(huì)有個(gè)疑問遮糖,
StickyEvent=true的訂閱者能否接收post的事件?
StickyEvent=false的訂閱者能否接收postSticky的事件?
查看源碼發(fā)現(xiàn)
/**
* 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);
}
}
發(fā)現(xiàn)殿较,post方法沒有過濾StickyEvent斜脂,而postSticky是調(diào)用post方法的,所以,無論post還是postSticky蔬胯,StickyEvent是true或false,都會(huì)執(zhí)行
4.priority事件優(yōu)先級(jí)
//priority越大舞竿,級(jí)別越高
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}
//優(yōu)先級(jí)實(shí)現(xiàn)方式,遍歷當(dāng)前列表执桌,把當(dāng)前
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.中止事件傳遞
// 中止事件傳遞伴逸,后續(xù)事件不在調(diào)用,注意错蝴,只能在傳遞事件的時(shí)候調(diào)用
@Subscribe
public void onEvent(MessageEvent event){
…
EventBus.getDefault().cancelEventDelivery(event) ;
}
6.index索引加速
EventBus使用了annotation,默認(rèn)在編譯時(shí)生成代碼,生成索引玛界,
添加index后會(huì)在編譯時(shí)運(yùn)行,自動(dòng)生成相應(yīng)代碼笨枯。
ps:由于apt的限制严嗜,匿名內(nèi)部類中的annotation不會(huì)被識(shí)別,會(huì)自動(dòng)降級(jí)在運(yùn)行時(shí)反射压彭,此時(shí)汗盘,效率會(huì)降低
EventBus官網(wǎng)地址
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 applied
dependencies {
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:如果是多個(gè)模塊都需要加索引,則每個(gè)模塊都要加上上面三個(gè)apt,包引入可以不需要重復(fù)
這樣脆丁,編譯后就會(huì)自動(dòng)生成代碼
/** 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);
}
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
在編譯時(shí)通過反射生成event方法存在map中,在發(fā)送事件的時(shí)候,調(diào)用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();
//給默認(rèn)eventbus添加索引
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
//多個(gè)庫需要添加索引
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
7.NoSubscriberEvent
如果沒找到訂閱者事件,可以通過EventBusBuilder設(shè)置是否默認(rèn)發(fā)送NoSubscriberEvent,默認(rèn)是打開的
/**
* 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));
}
}
}
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.利弊
好處
簡(jiǎn)單,方便籍嘹,小巧辱士,文檔清晰颂碘,性能消耗少塔拳,可定制行強(qiáng)靠抑,耦合度低
壞處
耦合度太低
這絕對(duì)不是個(gè)笑話荠列,,EventBus的耦合太低了诉瓦,如果不加以控制管理,你會(huì)不知道猴贰,你發(fā)的消息到跑哪里去了瑟捣。也不知道你的這條消息迈套,會(huì)在哪里發(fā)出。如果你沒有很好的方法解決這個(gè)問題贵白,建議不好用太多禁荒。
使用建議
1勃痴、EventBus管理
EventBus運(yùn)行創(chuàng)建多個(gè),那么污它,明確事件的生命周期,根據(jù)不同生命周期使用不同的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、以事件為對(duì)象
將數(shù)據(jù)封裝到一個(gè)事件類。所有事件放到一個(gè)包下。如果事件太多础废,同個(gè)模塊的事件可以考慮使用靜態(tài)內(nèi)部類帘瞭,或者再分包。
public class Event {
public static class UserListEvent {
public List<User> users ;
}
public static class ItemListEvent {
public List<Item> items;
}
}
注意媒殉,不是相同類型就一定要作為一個(gè)事件封裝柄错,具體需要考慮業(yè)務(wù)情景跟代碼情況,比如事件行為不同疫萤、事件生命周期不同扯饶,如果有必要尾序,寫封裝成兩個(gè)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;
}
}