對(duì)于Android開(kāi)發(fā)老司機(jī)來(lái)說(shuō)肯定不會(huì)陌生,它是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架,開(kāi)發(fā)者可以通過(guò)極少的代碼去實(shí)現(xiàn)多個(gè)模塊之間的通信胚想,而不需要以層層傳遞接口的形式去單獨(dú)構(gòu)建通信橋梁。從而降低因多重回調(diào)導(dǎo)致的模塊間強(qiáng)耦合,同時(shí)避免產(chǎn)生大量?jī)?nèi)部類暇屋。它擁有使用方便,性能高洞辣,接入成本低和支持多線程的優(yōu)點(diǎn)撼泛,實(shí)乃模塊解耦、代碼重構(gòu)必備良藥爸吮。
作為Markus Junginger大神耗時(shí)4年打磨握玛、超過(guò)1億接入量、Github 9000+ star的明星級(jí)組件畜挥,分析EventBus的文章早已是數(shù)不勝數(shù)仔粥。在EventBus 3中引入了EventBusAnnotationProcessor
(注解分析生成索引)技術(shù),大大提高了EventBus的運(yùn)行效率蟹但。而分析這個(gè)加速器的資料在網(wǎng)上很少躯泰,因此本文會(huì)把重點(diǎn)放在分析這個(gè)EventBus 3的新特性上,同時(shí)分享一些踩坑經(jīng)驗(yàn)华糖,并結(jié)合源碼分析及UML圖麦向,以直觀的形式和大家一起學(xué)習(xí)EventBus 3的用法及運(yùn)行原理。
使用EventBus
1.導(dǎo)入組件
// 打開(kāi)App的build.gradle客叉,在dependencies中添加最新的EventBus依賴:
compile 'org.greenrobot:eventbus:3.0.0'
如果不需要索引加速的話诵竭,就可以直接跳到第二步了。而要應(yīng)用最新的EventBusAnnotationProcessor
則比較麻煩兼搏,因?yàn)樽⒔饨馕鲆蕾囉赼ndroid-apt-plugin卵慰。我們一步一步來(lái),首先在項(xiàng)目gradle的dependencies中引入apt編譯插件:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
然后在App的build.gradle中應(yīng)用apt插件佛呻,并設(shè)置apt生成的索引的包名和類名:
apply plugin: 'com.neenbedankt.android-apt'
apt {
arguments {
eventBusIndex "org.greenrobot.eventbusperf.MyEventBusIndex"
}
}
接著在App的dependencies中引入EventBusAnnotationProcessor:
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
//或者使用源碼
apt project(':EventBusAnnotationProcessor')
此時(shí)需要我們先編譯一次裳朋,生成索引類。編譯成功之后吓著,就會(huì)發(fā)現(xiàn)在\ProjectName\app\build\generated\source\apt\PakageName\
下看到通過(guò)注解分析生成的索引類鲤嫡,這樣我們便可以在初始化EventBus時(shí)應(yīng)用我們生成的索引了送挑。
2.初始化EventBus
EventBus默認(rèn)有一個(gè)單例,可以通過(guò)getDefault()
獲取暖眼,也可以通過(guò)EventBus.builder()
構(gòu)造自定義的EventBus惕耕,比如要應(yīng)用我們生成好的索引時(shí):
EventBus mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
如果想把自定義的設(shè)置應(yīng)用到EventBus默認(rèn)的單例中,則可以用installDefaultEventBus()方法:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
3.定義事件
所有能被實(shí)例化為Object的實(shí)例都可以作為事件罢荡。
在最新版的eventbus 3中如果用到了索引加速赡突,事件類的修飾符必須為public,不然編譯時(shí)會(huì)報(bào)錯(cuò):Subscriber method must be public区赵。
4.監(jiān)聽(tīng)事件
首先把作為訂閱事件的模塊通過(guò)EventBus注冊(cè)監(jiān)聽(tīng):
mEventBus.register(this);
在3.0之前惭缰,注冊(cè)監(jiān)聽(tīng)需要區(qū)分是否監(jiān)聽(tīng)黏性(sticky)事件,監(jiān)聽(tīng)EventBus事件的模塊需要實(shí)現(xiàn)以onEvent開(kāi)頭的方法笼才。如今改為在方法上添加注解的形式:
@Subscribe(threadMode = ThreadMode.POSTING, priority = 0, sticky = true)
public void handleEvent(DriverEvent event) {
Log.d(TAG, event.info);
}
注解有三個(gè)參數(shù)漱受,threadMode為回調(diào)所在的線程,priority為優(yōu)先級(jí)骡送,sticky為是否接收黏性事件昂羡。調(diào)度單位從類細(xì)化到了方法,對(duì)方法的命名也沒(méi)有了要求摔踱,方便混淆代碼虐先。但注冊(cè)了監(jiān)聽(tīng)的模塊必須有一個(gè)標(biāo)注了Subscribe注解方法,不然在register時(shí)會(huì)拋出異常:
Subscriber class XXX and its super classes have no public methods with the @Subscribe annotation
5.發(fā)送事件
調(diào)用post或者postSticky即可:
mEventBus.post(new DriverEvent("magnet:?xt=urn:btih……"));
在實(shí)際項(xiàng)目的使用中派敷,register和unregister通常與Activity和Fragment的生命周期相關(guān)蛹批,ThreadMode.MainThread可以很好地解決Android的界面刷新必須在UI線程的問(wèn)題,不需要再回調(diào)后用Handler中轉(zhuǎn)(EventBus中已經(jīng)自動(dòng)用Handler做了處理)篮愉,黏性事件可以很好地解決post與register同時(shí)執(zhí)行時(shí)的異步問(wèn)題(這個(gè)在原理中會(huì)說(shuō)到)腐芍,事件的傳遞也沒(méi)有序列化與反序列化的性能消耗,足以滿足我們大部分情況下的模塊間通信需求试躏。
EventBus原理分析
1.訂閱注冊(cè)(register)
簡(jiǎn)單來(lái)說(shuō)就是:根據(jù)訂閱者的類來(lái)找回調(diào)方法猪勇,把訂閱者和回調(diào)方法封裝成關(guān)系,并保存到相應(yīng)的數(shù)據(jù)結(jié)構(gòu)中颠蕴,為隨后的事件分發(fā)做好準(zhǔn)備泣刹,最后處理黏性事件:
//3.0版本的注冊(cè)
EventBus.getDefault().register(this);
//2.x版本的注冊(cè)
EventBus.getDefault().register(this);
EventBus.getDefault().register(this, 100);
EventBus.getDefault().registerSticky(this, 100);
EventBus.getDefault().registerSticky(this);
可以看到2.x版本中有四種注冊(cè)方法,區(qū)分了普通注冊(cè)和粘性事件注冊(cè),并且在注冊(cè)時(shí)可以選擇接收事件的優(yōu)先級(jí),這里我們就不對(duì)2.x版本做過(guò)多的研究了,如果想研究可以參照此篇文章.由于3.0版本將粘性事件以及訂閱事件的優(yōu)先級(jí)換了一種更好的實(shí)現(xiàn)方式,所以3.0版本中的注冊(cè)就變得簡(jiǎn)單,只有一個(gè)register()
方法即可.
public void register(Object subscriber) {
//首先獲得訂閱者的class對(duì)象
Class<?> subscriberClass = subscriber.getClass();
//通過(guò)subscriberMethodFinder來(lái)找到訂閱者訂閱了哪些事件.返回一個(gè)SubscriberMethod對(duì)象的List,SubscriberMethod
//里包含了這個(gè)方法的Method對(duì)象,以及將來(lái)響應(yīng)訂閱是在哪個(gè)線程的ThreadMode,以及訂閱的事件類型eventType,以及訂閱的優(yōu)
//先級(jí)priority,以及是否接收粘性sticky事件的boolean值.
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//訂閱
subscribe(subscriber, subscriberMethod);
}
}
}
可以看到register()``方法很簡(jiǎn)潔,代碼里的注釋也很清楚了,我們可以看出通過(guò)
subscriberMethodFinder.findSubscriberMethods(subscriberClass)方法就能返回一個(gè)
SubscriberMethod的對(duì)象,而
SubscriberMethod里包含了所有我們需要的接下來(lái)執(zhí)行
subscribe()的信息.所以我們先去看看
findSubscriberMethods()`是怎么實(shí)現(xiàn)的,然后我們?cè)偃リP(guān)注subscribe()。
SubscriberMethodFinder的實(shí)現(xiàn)
一句話來(lái)描述SubscriberMethodFinder
類就是用來(lái)查找和緩存訂閱者響應(yīng)函數(shù)的信息的類犀被。所以我們首先要知道怎么能獲得訂閱者響應(yīng)函數(shù)的相關(guān)信息项玛。在3.0版本中,EventBus
提供了一個(gè)EventBusAnnotationProcessor
注解處理器來(lái)在編譯期通過(guò)讀取@Subscribe()
注解并解析,處理其中所包含的信息,然后生成java類來(lái)保存所有訂閱者關(guān)于訂閱的信息,這樣就比在運(yùn)行時(shí)使用反射來(lái)獲得這些訂閱者的信息速度要快.我們可以參考EventBus項(xiàng)目里的EventBusPerformance這個(gè)例子,編譯后我們可以在build文件夾里找到這個(gè)類,MyEventBusIndex 類,當(dāng)然類名是可以自定義的.我們大致看一下生成的MyEventBusIndex
類是什么樣的:
/**
* 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(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
true, new SubscriberMethodInfo[]{
new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
}));
putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[]{
new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
}));
}
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;
}
}
}
可以看出是使用一個(gè)靜態(tài)HashMap
即:SUBSCRIBER_INDEX
來(lái)保存訂閱類的信息,其中包括了訂閱類的class對(duì)象,是否需要檢查父類,以及訂閱方法的信息SubscriberMethodInfo
的數(shù)組,SubscriberMethodInfo
中又保存了:訂閱方法的方法名,訂閱的事件類型,觸發(fā)線程,是否接收sticky事件以及優(yōu)先級(jí)priority
.這其中就保存了register()的所有需要的信息,如果再配置EventBus的時(shí)候通過(guò)EventBusBuilder配置:eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build()
;來(lái)將編譯生成的MyEventBusIndex
配置進(jìn)去,這樣就能在SubscriberMethodFinder
類中直接查找出訂閱類的信息,就不需要再利用注解判斷了(也就是我們說(shuō)的渦輪引擎),當(dāng)然這種方法是作為EventBus的可選配置,SubscriberMethodFinder
同樣提供了通過(guò)注解來(lái)獲得訂閱類信息的方法,下面我們就來(lái)看findSubscriberMethods()
到底是如何實(shí)現(xiàn)的:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//先從METHOD_CACHE取看是否有緩存,key:保存訂閱類的類名,value:保存類中訂閱的方法數(shù)據(jù),
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//是否忽略注解器生成的MyEventBusIndex類
if (ignoreGeneratedIndex) {
//利用反射來(lái)讀取訂閱類中的訂閱方法信息
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//從注解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//保存進(jìn)METHOD_CACHE緩存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
注釋很詳細(xì)我們就不在多說(shuō),由于篇幅原因我們就不在分析findUsingInfo()
方法,其無(wú)非就是通過(guò)查找我們上面所說(shuō)的MyEventBusIndex
類中的信息,來(lái)轉(zhuǎn)換成List<SubscriberMethod>
從而獲得訂閱類的相關(guān)訂閱函數(shù)的各種信息.有興趣的可以自己研究看看,下面我們就來(lái)看findUsingReflection()
方法是如何實(shí)現(xiàn)的:
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
//FindState 用來(lái)做訂閱方法的校驗(yàn)和保存
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//通過(guò)反射來(lái)獲得訂閱方法信息
findUsingReflectionInSingleClass(findState);
//查找父類的訂閱方法
findState.moveToSuperclass();
}
//獲取findState中的SubscriberMethod(也就是訂閱方法List)并返回
return getMethodsAndRelease(findState);
}
這里通過(guò)FindState
類來(lái)做訂閱方法的校驗(yàn)和保存,并通過(guò)FIND_STATE_POOL
靜態(tài)數(shù)組來(lái)保存FindState
對(duì)象,可以使FindState
復(fù)用,避免重復(fù)創(chuàng)建過(guò)多的對(duì)象.最終是通過(guò)findUsingReflectionInSingleClass()
來(lái)具體獲得相關(guān)訂閱方法的信息的:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
//通過(guò)反射得到方法數(shù)組
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;
}
//遍歷Method
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
//保證必須只有一個(gè)事件參數(shù)
if (parameterTypes.length == 1) {
//得到注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
//校驗(yàn)是否添加該方法
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//實(shí)例化SubscriberMethod對(duì)象并添加
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");
}
}
}
這里走完,我們訂閱類的所有SubscriberMethod
都已經(jīng)被保存了,最后再通過(guò)getMethodsAndRelease()
返回List<SubscriberMethod>
至此,所有關(guān)于如何獲得訂閱類的訂閱方法信息即:SubscriberMethod
對(duì)象就已經(jīng)完全分析完了,下面我們來(lái)看subscribe()是如何實(shí)現(xiàn)的.
subscribe()方法的實(shí)現(xiàn)
這里我們回到subscribe(subscriber, subscriberMethod);
中去,通過(guò)這個(gè)方法,我們就完成了注冊(cè),下面看一下subscribe()
的實(shí)現(xiàn):
//必須在同步代碼塊里調(diào)用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//獲取訂閱的事件類型
Class<?> eventType = subscriberMethod.eventType;
//創(chuàng)建Subscription對(duì)象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//從subscriptionsByEventType里檢查是否已經(jīng)添加過(guò)該Subscription,如果添加過(guò)就拋出異常
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//根據(jù)優(yōu)先級(jí)priority來(lái)添加Subscription對(duì)象
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;
}
}
//將訂閱者對(duì)象以及訂閱的事件保存到typesBySubscriber里.
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//如果接收sticky事件,立即分發(fā)sticky事件
if (subscriberMethod.sticky) {
//eventInheritance 表示是否分發(fā)訂閱了響應(yīng)事件類父類事件的方法
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);
}
}
}
以上就是所有注冊(cè)過(guò)程,現(xiàn)在再來(lái)看這張圖就會(huì)特別清晰EventBus的register()過(guò)程了:
2.事件分發(fā)(post)
我們知道可以通過(guò)EventBus.getDefault().post("str");來(lái)發(fā)送一個(gè)事件,所以我們就從這行代碼開(kāi)始分析,首先看看post()方法是如何實(shí)現(xiàn)的:
public void post(Object event) {
//得到當(dāng)前線程的Posting狀態(tài).
PostingThreadState postingState = currentPostingThreadState.get();
//獲取當(dāng)前線程的事件隊(duì)列
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
//一直發(fā)送
while (!eventQueue.isEmpty()) {
//發(fā)送單個(gè)事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
首先是通過(guò)currentPostingThreadState.get()
方法來(lái)得到當(dāng)前線程PostingThreadState
的對(duì)象,為什么是說(shuō)當(dāng)前線程我們來(lái)看看currentPostingThreadState
的實(shí)現(xiàn):
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
currentPostingThreadState
的實(shí)現(xiàn)是一個(gè)包含了PostingThreadState
的ThreadLocal
對(duì)象,關(guān)于ThreadLocal
張濤的這篇文章解釋的很好:ThreadLocal 是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù)弱判,而這段數(shù)據(jù)是不會(huì)與其他線程共享的。其內(nèi)部原理是通過(guò)生成一個(gè)它包裹的泛型對(duì)象的數(shù)組锥惋,在不同的線程會(huì)有不同的數(shù)組索引值昌腰,通過(guò)這樣就可以做到每個(gè)線程通過(guò)get() 方法獲取的時(shí)候开伏,取到的只能是自己線程所對(duì)應(yīng)的數(shù)據(jù)。 所以這里取到的就是每個(gè)線程的PostingThreadState
狀態(tài).接下來(lái)我們來(lái)看postSingleEvent()
方法:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
//是否觸發(fā)訂閱了該事件(eventClass)的父類,以及接口的類的響應(yīng)方法.
if (eventInheritance) {
//查找eventClass類所有的父類以及接口
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
//循環(huán)postSingleEventForEventType
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
//只要右邊有一個(gè)為true,subscriptionFound就為true
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//post單個(gè)
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//如果沒(méi)發(fā)現(xiàn)
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
//發(fā)送一個(gè)NoSubscriberEvent事件,如果我們需要處理這種狀態(tài),接收這個(gè)事件就可以了
post(new NoSubscriberEvent(this, event));
}
}
}
跟著上面的代碼的注釋,我們可以很清楚的發(fā)現(xiàn)是在postSingleEventForEventType()
方法里去進(jìn)行事件的分發(fā),代碼如下:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
//獲取訂閱了這個(gè)事件的Subscription列表.
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 {
//分發(fā)給訂閱者
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;
}
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);
}
}
總結(jié)上面的代碼就是,首先從subscriptionsByEventType
里獲得所有訂閱了這個(gè)事件的Subscription
列表,然后在通過(guò)postToSubscription()
方法來(lái)分發(fā)事件,在postToSubscription()
通過(guò)不同的threadMode
在不同的線程里invoke()
訂閱者的方法,ThreadMode
共有四類:
- PostThread
默認(rèn)的 ThreadMode遭商,表示在執(zhí)行 Post 操作的線程直接調(diào)用訂閱者的事件響應(yīng)方法固灵,不論該線程是否為主線程(UI 線程)。當(dāng)該線程為主線程時(shí)劫流,響應(yīng)方法中不能有耗時(shí)操作巫玻,否則有卡主線程的風(fēng)險(xiǎn)。適用場(chǎng)景:對(duì)于是否在主線程執(zhí)行無(wú)要求祠汇,但若 Post 線程為主線程仍秤,不能耗時(shí)的操作; - MainThread
在主線程中執(zhí)行響應(yīng)方法可很。如果發(fā)布線程就是主線程诗力,則直接調(diào)用訂閱者的事件響應(yīng)方法,否則通過(guò)主線程的 Handler 發(fā)送消息在主線程中處理——調(diào)用訂閱者的事件響應(yīng)函數(shù)我抠。顯然苇本,MainThread
類的方法也不能有耗時(shí)操作,以避免卡主線程菜拓。適用場(chǎng)景:必須在主線程執(zhí)行的操作瓣窄; - BackgroundThread
在后臺(tái)線程中執(zhí)行響應(yīng)方法。如果發(fā)布線程不是主線程纳鼎,則直接調(diào)用訂閱者的事件響應(yīng)函數(shù)俺夕,否則啟動(dòng)唯一的后臺(tái)線程去處理。由于后臺(tái)線程是唯一的喷橙,當(dāng)事件超過(guò)一個(gè)的時(shí)候啥么,它們會(huì)被放在隊(duì)列中依次執(zhí)行,因此該類響應(yīng)方法雖然沒(méi)有PostThread
類和MainThread
類方法對(duì)性能敏感贰逾,但最好不要有重度耗時(shí)的操作或太頻繁的輕度耗時(shí)操作悬荣,以造成其他操作等待。適用場(chǎng)景:操作輕微耗時(shí)且不會(huì)過(guò)于頻繁疙剑,即一般的耗時(shí)操作都可以放在這里氯迂; - Async
不論發(fā)布線程是否為主線程,都使用一個(gè)空閑線程來(lái)處理言缤。和BackgroundThread
不同的是嚼蚀,Async
類的所有線程是相互獨(dú)立的,因此不會(huì)出現(xiàn)卡線程的問(wèn)題管挟。適用場(chǎng)景:長(zhǎng)耗時(shí)操作轿曙,例如網(wǎng)絡(luò)訪問(wèn)。
這里我們只來(lái)看看invokeSubscriber(subscription, event);
是如何實(shí)現(xiàn)的,關(guān)于不同線程的Poster的使用
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
實(shí)際上就是通過(guò)反射調(diào)用了訂閱者的訂閱函數(shù)并把event
對(duì)象作為參數(shù)傳入.至此post()
流程就結(jié)束了,整體流程圖如下:
3.取消訂閱(unregister)
看完了上面的分析,解除注冊(cè)就相對(duì)容易了,解除注冊(cè)只要調(diào)用unregister()
方法即可,實(shí)現(xiàn)如下:
public synchronized void unregister(Object subscriber) {
//通過(guò)typesBySubscriber來(lái)取出這個(gè)subscriber訂閱者訂閱的事件類型,
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
//分別解除每個(gè)訂閱了的事件類型
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
//從typesBySubscriber移除subscriber
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
然后接著看unsubscribeByEventType()
方法的實(shí)現(xiàn):
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
//subscriptionsByEventType里拿出這個(gè)事件類型的訂閱者列表.
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
//取消訂閱
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
最終分別從typesBySubscriber
和subscriptions
里分別移除訂閱者以及相關(guān)信息即可.
觀察者模式觀察者模式是對(duì)象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式导帝、模型-視圖(Model/View)模式守谓、源-監(jiān)聽(tīng)器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對(duì)多的依賴關(guān)系您单,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象斋荞。這個(gè)主題對(duì)象在狀態(tài)上發(fā)生變化時(shí),會(huì)通知所有觀察者對(duì)象虐秦,使它們能夠自動(dòng)更新自己平酿。EventBus
并不是標(biāo)準(zhǔn)的觀察者模式的實(shí)現(xiàn),但是它的整體就是一個(gè)發(fā)布/訂閱框架,也擁有觀察者模式的優(yōu)點(diǎn),比如:發(fā)布者和訂閱者的解耦.
4.拓展-注解
注解(Annotations)是一種元數(shù)據(jù)的格式,為程序提供數(shù)據(jù)悦陋,但又不是程序的一部分蜈彼。注解(Annotations)在代碼上不直接影響代碼操作。這怎么理解是數(shù)據(jù)又不是程序的一部分叨恨?我們可以將注解看成一種標(biāo)注柳刮,類似于注釋// 或 /* */
,不是程序的一部分但為程序提供注釋痒钝,注解可以看作是提供數(shù)據(jù)秉颗。下面我們來(lái)看一下注解的用途:
- 提供編譯信息:注解可以在編譯的時(shí)候檢查錯(cuò)誤和提示警告(java語(yǔ)言規(guī)范性看來(lái)和注解不無(wú)關(guān)系)
- 編譯和部署時(shí)處理:軟件工具可以通過(guò)注解生成代碼、xml文件等(注入看來(lái)也很黑科技)
- 運(yùn)行時(shí)處理:一些注解可以在運(yùn)行時(shí)使用
注解基礎(chǔ)
注解是什么格式呢送矩?
@Entity
這就是注解蚕甥,看起來(lái)很簡(jiǎn)單,通過(guò)設(shè)置(@)符號(hào)標(biāo)識(shí)栋荸,在跟一個(gè)注解的名稱菇怀。下面來(lái)看一下我們經(jīng)常用的Override
注解。
@Override
void mySuperMethod() { ... }
再來(lái)看一下帶有參數(shù)的注解:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() { ... }
or
@SuppressWarnings(value = "unchecked")
void myMethod() { ... }
如果按照上述value = "unchecked"
是不是參數(shù)只能限定一個(gè)晌块,如果多個(gè)怎么辦:
@SuppressWarnings("unchecked")
void myMethod() { ... }
其實(shí)還有一種能夠傳入多個(gè)值(Java SE 8 release支持)爱沟,:
@Author(name = "Jane Doe") @Author(name = "John Smith") class MyClass { ... }
注解可以用在什么地方呢?注解可以用應(yīng)用在:聲明一個(gè)類匆背、變量呼伸、方法和程序元素。
聲明注解類型
如何做一個(gè)我們自己的注解钝尸?這里來(lái)看一個(gè)常見(jiàn)類:
public class Generation3List extends Generation2List {
// Author: John Doe
// Date: 3/17/2002
// Current revision: 6
// Last modified: 4/12/2004
// By: Jane Doe
// Reviewers: Alice, Bill, Cindy
// class code goes here
}
這里使用相同的元數(shù)據(jù)括享,通過(guò)注解來(lái)實(shí)現(xiàn),我們必須定義注解類型珍促。語(yǔ)法如下:
@interface ClassPreamble {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}
注解的定義非常類似接口铃辖,只是將接口的interface
關(guān)鍵字替換為@interface
。注解也是接口的一種形式猪叙。如何使用注解呢娇斩?注解的使用在聲明定義前使用:
@ClassPreamble (
author = "John Doe",
date = "3/17/2002",
currentRevision = 6,
lastModified = "4/12/2004",
lastModifiedBy = "Jane Doe",
// Note array notation
reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {
// class code goes here
}
常用的注解類型
注解實(shí)際上被meta-annotations
調(diào)用仁卷。幾種meta-annotations
在 java.lang.annotation.
類中。
- @Retention 定義一種注解的存儲(chǔ)方式
- -RetentionPolicy.SOURCE 注解以源碼級(jí)別存儲(chǔ)成洗,忽略編譯
- -RetentionPolicy.CLASS 注解在編譯時(shí)保留五督,忽略Java Virtual Machine (JVM)
- -RetentionPolicy.RUNTIME 運(yùn)行時(shí)注解
- @Documented標(biāo)識(shí)注解可以使用Javadoc tool工具將其生成文檔
- @Target標(biāo)識(shí)注解將要被應(yīng)用成哪種Java元素,其中包含有:
- -ElementType.ANNOTATION_TYPE
- -ElementType.CONSTRUCTOR
- -ElementType.FIELD
- -ElementType.LOCAL_VARIABLE
- -ElementType.METHOD
- -ElementType.PACKAGE
- -ElementType.PARAMETER
- -ElementType.TYPE 任意一種類形式
- @Inherited標(biāo)識(shí)注解是否可以被子類集成(true或false瓶殃,默認(rèn)false),當(dāng)用戶查詢注解類型副签,該類沒(méi)有聲明注解類型遥椿,該類的父類查詢到該注解類型。該注解僅應(yīng)用在當(dāng)前聲明類
- @RepeatableJava SE 8引入淆储,標(biāo)識(shí)注解可以用應(yīng)用多個(gè)相同的聲明冠场,具體參考:Repeating Annotations.
EventBus對(duì)注解的使用
先來(lái)看一下注解類聲明:
@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;
}
注解支持文檔輸出,存在于運(yùn)行時(shí)本砰,對(duì)Java方法生效碴裙。在使用的時(shí)候如下代碼:
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()));
}
}
獲取方法聲明的注解,通過(guò)subscribeAnnotation.threadMode()
拿到對(duì)應(yīng)的ThreadMode
点额,通過(guò)subscribeAnnotation.priority()
拿到聲明的優(yōu)先級(jí)舔株,通過(guò)subscribeAnnotation.sticky()
拿到是否是粘性廣播。在方法前的聲明如下还棱,聲明非常簡(jiǎn)單:
@Subscribe(threadMode = ThreadMode.MAIN)
5.渦輪引擎-apt注入
apt注入可以重是一種編譯時(shí)注解载慈,如何在gradle中進(jìn)行動(dòng)態(tài)注入呢?android-apt
是一個(gè)Gradle插件珍手,協(xié)助Android Studio
處理annotation processors
, 它有兩個(gè)目的:
- 允許配置只在編譯時(shí)作為注解處理器的依賴办铡,而不添加到最后的APK或library
- 設(shè)置源路徑,使注解處理器生成的代碼能被Android Studio正確的引用
在EventBus上琳要,主要使用注解處理器生成的代碼寡具,將所有注解的內(nèi)容獨(dú)立到org.greenrobot.eventbusperf.MyEventBusIndex
類中,
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
try {
// 獲取腳本中聲明變量eventBusIndex
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
....
// 收集訂閱的聲明
collectSubscribers(annotations, env, messager);
// 檢查訂閱聲明
checkForSubscribersToSkip(messager, indexPackage);
if (!methodsByClass.isEmpty()) {
// 將訂閱聲明寫入到索引文件中
createInfoIndexFile(index);
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
writerRoundDone = true;
} catch (RuntimeException e) {
// IntelliJ does not handle exceptions nicely, so log and print a message
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
}
return true;
}
關(guān)鍵就是將索引到的所有符合要求的注解類稚补,寫入到索引文件中童叠,代碼如下所示:
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf('.');
String myPackage = period > 0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
if (myPackage != null) {
writer.write("package " + myPackage + ";\n\n");
}
writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
writer.write("import java.util.HashMap;\n");
writer.write("import java.util.Map;\n\n");
writer.write("/** This class is generated by EventBus, do not edit. */\n");
writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
writer.write(" private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
writeIndexLines(writer, myPackage);
writer.write(" }\n\n");
writer.write(" private static void putIndex(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
writer.write(" }\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;\n");
writer.write(" } else {\n");
writer.write(" return null;\n");
writer.write(" }\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
throw new RuntimeException("Could not write source for " + index, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
//Silent
}
}
}
}
踩坑與經(jīng)驗(yàn)
1.混淆問(wèn)題
混淆作為版本發(fā)布必備的流程,經(jīng)常會(huì)鬧出很多奇奇怪怪的問(wèn)題孔厉,且不方便定位拯钻,尤其是EventBus這種依賴反射技術(shù)的庫(kù)。通常情況下都會(huì)把相關(guān)的類和回調(diào)方法都keep住撰豺,但這樣其實(shí)會(huì)留下被人反編譯后破解的后顧之憂粪般,所以我們的目標(biāo)是keep最少的代碼。
首先污桦,因?yàn)镋ventBus 3棄用了反射的方式去尋找回調(diào)方法亩歹,改用注解的方式。作者的意思是在混淆時(shí)就不用再keep住相應(yīng)的類和方法。但是我們?cè)谶\(yùn)行時(shí)小作,卻會(huì)報(bào)java.lang.NoSuchFieldError: No static field POSTING亭姥。
網(wǎng)上給出的解決辦法是keep住所有eventbus相關(guān)的代碼:
-keep class de.greenrobot.** {*;}
其實(shí)我們仔細(xì)分析,可以看到是因?yàn)樵赟ubscriberMethodFinder的findUsingReflection方法中顾稀,在調(diào)用Method.getAnnotation()時(shí)獲取ThreadMode這個(gè)enum失敗了达罗,所以我們只需要keep住這個(gè)enum就可以了(如下)。
-keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }
這樣就能正常編譯通過(guò)了静秆,但如果使用了索引加速粮揉,是不會(huì)有上面這個(gè)問(wèn)題的。因?yàn)樵谡曳椒〞r(shí)抚笔,調(diào)用的不是findUsingReflection扶认,而是findUsingInfo。但是使用了索引加速后殊橙,編譯后卻會(huì)報(bào)新的錯(cuò)誤:Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?
這就很好理解了辐宾,因?yàn)樯伤饕鼼eneratedSubscriberIndex是在代碼混淆之前進(jìn)行的,混淆之后類名和方法名都不一樣了(上面這個(gè)錯(cuò)誤是方法無(wú)法找到)膨蛮,得keep住所有被Subscribe注解標(biāo)注的方法:
-keepclassmembers class * {
@de.greenrobot.event.Subscribe <methods>;
}
所以又倒退回了EventBus2.4時(shí)不能混淆onEvent開(kāi)頭的方法一樣的處境了叠纹。所以這里就得權(quán)衡一下利弊:使用了注解不用索引加速,則只需要keep住EventBus相關(guān)的代碼鸽疾,現(xiàn)有的代碼可以正常的進(jìn)行混淆吊洼。而使用了索引加速的話,則需要keep住相關(guān)的方法和類制肮。
2.跨進(jìn)程問(wèn)題
目前EventBus只支持跨線程冒窍,而不支持跨進(jìn)程。如果一個(gè)app的service起到了另一個(gè)進(jìn)程中豺鼻,那么注冊(cè)監(jiān)聽(tīng)的模塊則會(huì)收不到另一個(gè)進(jìn)程的EventBus發(fā)出的事件综液。這里可以考慮利用IPC做映射表,并在兩個(gè)進(jìn)程中各維護(hù)一個(gè)EventBus儒飒,不過(guò)這樣就要自己去維護(hù)register和unregister的關(guān)系谬莹,比較繁瑣,而且這種情況下通常用廣播會(huì)更加方便桩了,大家可以思考一下有沒(méi)有更優(yōu)的解決方案附帽。
3.事件環(huán)路問(wèn)題
在使用EventBus時(shí),通常我們會(huì)把兩個(gè)模塊相互監(jiān)聽(tīng)井誉,來(lái)達(dá)到一個(gè)相互回調(diào)通信的目的蕉扮。但這樣一旦出現(xiàn)死循環(huán),而且如果沒(méi)有相應(yīng)的日志信息颗圣,很難定位問(wèn)題喳钟。所以在使用EventBus的模塊屁使,如果在回調(diào)上有環(huán)路,而且回調(diào)方法復(fù)雜到了一定程度的話奔则,就要考慮把接收事件專門封裝成一個(gè)子模塊蛮寂,同時(shí)考慮避免出現(xiàn)事件環(huán)路。
老司機(jī)教你 “飆” EventBus 3
EventBus-3-0源碼分析
注解參考
android-apt 即將退出歷史舞臺(tái)
EventBus Documentation