面試有一種技巧據(jù)說叫做反客為主玫鸟,當(dāng)遇到Activity-Fragment通信导绷,甚至模塊化開發(fā)時的通信問題等等,可以將TA引導(dǎo)到你擅長的領(lǐng)域中來屎飘,比如說用EventBus來解決妥曲,(RxBus也可以)這時一般套路都會問你懂不懂EventBus的原理,這時你就可以以下文的姿勢這樣回(zhuang)答(b)了
(??????)??
簡介
今天我們來分析截止到現(xiàn)在的3.0.0最新版本
慣例介紹一下我們今天的主角钦购,EventBus檐盟,專門管理Android事件通訊。
//依賴
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
}
在Android開發(fā)中難免會遇到以下場景:
- Activity之間的回調(diào)
startActivityForResult
- 某個頁面刷新后聯(lián)動其他頁面也刷新
- Fragment之間的通信
- more
按筆者之前的尿性就是復(fù)雜場景用本地廣播做發(fā)送與監(jiān)聽押桃,廣播大吼一聲葵萎,我叫你一聲你敢答應(yīng)嗎(??????)??,是不是很有代入感唱凯,直到后來發(fā)現(xiàn)廣播的性能與Intent傳遞數(shù)據(jù)大小有限制以及相對難以維護(hù)后來吃下了EventBus這發(fā)安利羡忘。
筆者先簡單的寫個基本用法
需要接收事件的類中注冊監(jiān)聽(不局限Activity)
EventBus.getDefault().register(this);
別忘記在銷毀時反注冊
EventBus.getDefault().unregister(this);
發(fā)送事件
EventBus.getDefault().post(Object event);
在需要接收事件的類中隨便寫個方法,例如
//別忘了這個注解
@Subscribe
public void onEventReceive(String event) {
}
這個方法只要一個參數(shù)就夠了磕昼,由于我們發(fā)送事件的時候EventBus根據(jù)類型來做校驗卷雕,例如post("test"),此時所有注冊的類中帶有Subscribe注解的方法中只要第一個參數(shù)是String類型票从,那么就會調(diào)用這個方法漫雕,你問我怎么知道的滨嘱,我等會再告訴你(__)
淺析
在EventBus光鮮的外表下我們好像看到了觀察者模式的影子,沒錯浸间,我們來從TA的入口開始EventBus.getDefault().register(this);
中挨個的調(diào)用順序
public class EventBus {
//暫時省略一些代碼
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
public EventBus() {
this(DEFAULT_BUILDER);
}
}
一個常見的DCL單例模式+構(gòu)建者模式太雨,我們再來看看EventBusBuilder
的構(gòu)造器
public class EventBusBuilder {
//默認(rèn)的線程池
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
//是否打印出訂閱時拋出異常的日志
boolean logSubscriberExceptions = true;
//是否打印出沒有訂閱者異常的日志
boolean logNoSubscriberMessages = true;
//當(dāng)訂閱者訂閱方法執(zhí)行拋出異常時 ,是否讓EventBus發(fā)送一個特定事件
boolean sendSubscriberExceptionEvent = true;
//當(dāng)事件沒有訂閱者時魁蒜,是否讓EventBus發(fā)送一個特定事件
boolean sendNoSubscriberEvent = true;
//是否拋出訂閱者的異常
boolean throwSubscriberException;
//是否允許事件可以有繼承
boolean eventInheritance = true;
//是否忽視生成的索引
boolean ignoreGeneratedIndex;
//是否方法嚴(yán)格校驗
boolean strictMethodVerification;
//線程池
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
//某些類跳過驗證躺彬,類似綠色通道
List<Class<?>> skipMethodVerificationForClasses;
//訂閱者的索引集合
List<SubscriberInfoIndex> subscriberInfoIndexes;
EventBusBuilder() {
}
}
貼心的我又都給你們打上注釋了,一下子看不明白沒關(guān)系梅惯,后面我們還會再見面的
//EventBus的一參構(gòu)造器
EventBus(EventBusBuilder builder) {
//根據(jù)消息類型的訂閱者們,key對應(yīng)消息的類型仿野,value對應(yīng)訂閱這個類型消息的訂閱者們
subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>();
//根據(jù)訂閱者的類型們铣减,key對應(yīng)訂閱者,value對應(yīng)在這個訂閱者訂閱的事件類型們
typesBySubscriber = new HashMap<Object, List<Class<?>>>();
//粘性事件脚作,key對應(yīng)事件類型葫哗,valve對應(yīng)訂閱者
stickyEvents = new ConcurrentHashMap<Class<?>, Object>();
//主線程的發(fā)送器,本質(zhì)是Handler
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
//后臺線程的發(fā)送器球涛,本質(zhì)是Runnable
backgroundPoster = new BackgroundPoster(this);
//異步線程的發(fā)送器劣针,本質(zhì)是Runnable
asyncPoster = new AsyncPoster(this);
//訂閱者索引數(shù)量
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
//訂閱者方法搜尋器
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
//↓接受builder的變量
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
如果看官稍微留意一點(diǎn)注釋,相信已經(jīng)對EventBus有所猜測了亿扁,沒錯捺典,在register
方法中EventBus肯定對訂閱者進(jìn)行了不為人知的操作,將訂閱者和接受的事件等等統(tǒng)統(tǒng)做了記錄从祝,抱著這樣的猜測我們來瞧瞧register
public void register(Object subscriber) {
//獲取class
Class<?> subscriberClass = subscriber.getClass();
//搜尋器去尋找訂閱者要訂閱的的集合
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//同步遍歷后一一訂閱
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
這里可以這樣打個比方襟己,subscriber
比作小明
,小明
想訂報紙牍陌,他去找EventBus擎浴,說我要訂報紙,然后EventBus派遣搜尋器小剛
去整理你要的類型毒涧,一看你原來要訂旅游(String)贮预,拍照(Integer)和運(yùn)動(Boolean),然后就去專門的專欄一一訂閱契讲,以后這些專欄一更新仿吞,EventBus就把報紙送到你的手上。
這里我們來看看subscriberMethodFinder
的初始化捡偏,初始化就在上文EventBus的構(gòu)造器中
SubscriberMethodFinder(List<SubscriberInfoIndex> subscriberInfoIndexes, boolean strictMethodVerification,
boolean ignoreGeneratedIndex) {
this.subscriberInfoIndexes = subscriberInfoIndexes;
this.strictMethodVerification = strictMethodVerification;
this.ignoreGeneratedIndex = ignoreGeneratedIndex;
}
將訂閱者的索引集合茫藏,是否方法嚴(yán)格校驗,是否忽視索引3個參數(shù)傳入霹琼,有關(guān)索引做這塊先賣個關(guān)子务傲,先提前劇透一下凉当,今天是小明
要訂報紙,明天又來個小張
售葡,小剛
每次拿筆都要記一下再去專欄訂閱看杭,小張
覺得太累了,于是想了個法子挟伙,每次要來訂報紙楼雹,你們先填張表,填完給我尖阔,這樣將就省事多了贮缅。
深入
接下來我們來看看搜尋器的尋找方法有沒有什么貓膩
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//優(yōu)先先從訂閱者的緩存集合中尋找
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
//根據(jù)名字,應(yīng)該是反射獲取對應(yīng)的訂閱方法
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//默認(rèn)不忽視介却,也就是從索引中尋找
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
//如果訂閱者沒有對應(yīng)訂閱方法異常
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//壓入緩存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
同理我們先跳過索引谴供,先來分析findUsingReflection
方法
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
//保存記錄
FindState findState = prepareFindState();
//初始化
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//通過反射來獲取訂閱方法
findUsingReflectionInSingleClass(findState);
//移動到父類
findState.moveToSuperclass();
}
//將最后結(jié)果返回并回收資源
return getMethodsAndRelease(findState);
}
FindState
就在SubscriberMethodFinder
下作為靜態(tài)內(nèi)部類,具體如下
static class FindState {
//尋找到的訂閱方法
final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
//所有符合事件類型的方法齿坷,包含父類的
//key對應(yīng)事件類型桂肌,value對應(yīng)方法Method
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
//訂閱者可接受的
//key對應(yīng)字符串Method名稱+">"+事件類型名稱,value對應(yīng)Method的聲明類
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
//拼接
final StringBuilder methodKeyBuilder = new StringBuilder(128);
//訂閱者的類型
Class<?> subscriberClass;
//記錄的當(dāng)前的類
Class<?> clazz;
//是否跳過父類
boolean skipSuperClasses;
//索引
SubscriberInfo subscriberInfo;
//初始化
void initForSubscriber(Class<?> subscriberClass) {
this.subscriberClass = clazz = subscriberClass;
skipSuperClasses = false;
subscriberInfo = null;
}
//回收資源
void recycle() {
subscriberMethods.clear();
anyMethodByEventType.clear();
subscriberClassByMethodKey.clear();
methodKeyBuilder.setLength(0);
subscriberClass = null;
clazz = null;
skipSuperClasses = false;
subscriberInfo = null;
}
//通過方法和事件類型是否能夠添加到隊列中
boolean checkAdd(Method method, Class<?> eventType) {
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();
}
anyMethodByEventType.put(eventType, this);
}
return checkAddWithMethodSignature(method, eventType);
}
}
//通過方法和事件類型是否能夠添加到隊列中崎场,二次校驗
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
//按照指定格式拼接作為String key
String methodKey = methodKeyBuilder.toString();
Class<?> methodClass = method.getDeclaringClass();
Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
//集合未添加過 || 添加過并且后續(xù)添加的訂閱者方法聲明類和之前覆蓋的值有子父關(guān)系或者同屬一類
return true;
} else {
//添加過了且沒有類關(guān)系
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
//移動到父類
void moveToSuperclass() {
if (skipSuperClasses) {
clazz = null;
} else {
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
//跳過系統(tǒng)類
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
clazz = null;
}
}
}
}
理解起來比較雞肋,沒什么營養(yǎng)遂蛀,我們結(jié)合findUsingReflectionInSingleClass
一起來看
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
//獲取當(dāng)前類中的聲明方法集合
//原文注釋說比下面的效率高
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;
}
for (Method method : methods) {
//開始遍歷
//方法修飾符
int modifiers = method.getModifiers();
//方法只能是public谭跨,并且不能為static,abstract和2個筆者不懂的迷之修飾符_(:з」∠)_
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//獲取方法需要的參數(shù)
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
//一個參數(shù)
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
//判斷是否可以添加
if (findState.checkAdd(method, eventType)) {
//提取注解的線程修飾mode
ThreadMode threadMode = subscribeAnnotation.threadMode();
//一并添加隊列中
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
//嚴(yán)格校驗下就要拋出異常
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)) {
//嚴(yán)格校驗下就要拋出異常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
仔細(xì)一看就明白多了李滴,在findUsingReflection
方法中先初始化了一個FindState
對象饺蚊,然后對訂閱者類型的父子繼承關(guān)系以及事件類型的匹配進(jìn)行層層篩選,將篩選結(jié)果作為List<SubscriberMethod>
返回給上級悬嗓,篩選過程比較復(fù)雜污呼,感興趣的同學(xué)可以參照筆者上面的注釋仔細(xì)品味。
總結(jié)如下:
- 訂閱的方法需要
@Subscribe
修飾包竹,里面可以附帶線程模式和和優(yōu)先級燕酷; - 訂閱方法必須為public,不能為abstract和static周瞎;
- 允許訂閱者之間的繼承關(guān)系苗缩;
其中SubscriberMethod
內(nèi)部存放一些事件的信息,我們一會再提
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
}
在將結(jié)果返回的方法getMethodsAndRelease
中声诸,還做了緩存處理
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}
好了酱讶,萬里長征已經(jīng)進(jìn)行了一半了,什么1宋凇?你和我說才一半
(╯‵□′)╯︵┻━┻。休里。。堅持一下琉朽,馬上就革命勝利了
┬─┬ ノ( ' - 'ノ)
我們回到EventBus的register
方法,下一步是加鎖遍歷搜尋器返回的結(jié)果稚铣,也就是小剛
要把小明
箱叁,小張
的訂閱表去訂閱道指定專欄
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//獲取事件類型
Class<?> eventType = subscriberMethod.eventType;
//一重簡單的封裝
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//從這個事件類型的map中取出訂閱者們
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//第一次的初始化
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
//已經(jīng)添加過,拋出異常
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
//根據(jù)訂閱者方法的優(yōu)先級存放
subscriptions.add(i, newSubscription);
break;
}
}
//將事件類型放入這個訂閱者所訂閱的類型集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
//訂閱方法是接受粘性事件時
if (eventInheritance) {
//消息允許繼承時
//遍歷map
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();
//判斷粘性事件是否立即分發(fā)
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
//判斷粘性事件是否立即分發(fā)
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
在subscribe
方法中對事件和訂閱者的關(guān)系都保存在了map中惕医,然后對粘性事件進(jìn)行了處理耕漱。
這里我們又遇到了粘性事件,打個比方抬伺,小張
訂閱了音樂
頻道的報紙螟够,想在訂閱時希望過往的報紙也能看到。其實和四大組件的粘性廣播相似沛简,又比如我們開啟一個Activity,我們在Activity的onCreate
中注冊了事件斥废,但是在傳遞數(shù)據(jù)時我們的消息已經(jīng)發(fā)出了椒楣,而Activity的初始化不是startActivity
一下就能開啟的,所以我們在Activity中的訂閱方法要改一改牡肉,將注解改成@Subscribe(sticky = true)
捧灰,就能接受到粘性事件。
那么EventBus的注冊方法我們從頭到尾看了一遍统锤,接下來我們來看看分發(fā)方法post
public void post(Object event) {
//獲取當(dāng)前線程的狀態(tài)
PostingThreadState postingState = currentPostingThreadState.get();
//當(dāng)前線程的消息隊列
List<Object> eventQueue = postingState.eventQueue;
//添加事件
eventQueue.add(event);
if (!postingState.isPosting) {
//當(dāng)前線程不處于發(fā)送中狀態(tài)
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
//循環(huán)發(fā)送隊列中事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
post
方法很簡單毛俏,隊列中有消息就發(fā)送出去,如何獲取當(dāng)前線程的狀態(tài)原理基于ThreadLocal
來實現(xiàn)饲窿,這里筆者簡要提一下好了煌寇,ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲一些數(shù)據(jù)逾雄,而這段數(shù)據(jù)是不與其他線程共享的阀溶。內(nèi)部原理是通過泛型對象數(shù)組,在不同的線程會有不同的數(shù)組索引值鸦泳,這樣就可以在不同線程這種調(diào)用get
方法時银锻,取到對應(yīng)線程的數(shù)據(jù)。
有關(guān)PostingThreadState
代碼如下
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
我們繼續(xù)順藤摸瓜來看看發(fā)送消息的具體實現(xiàn)
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
//事件是否找到對應(yīng)訂閱者
boolean subscriptionFound = false;
if (eventInheritance) {
//允許事件繼承的時候做鹰,找到它的父類們
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
//按位或击纬,把結(jié)果賦值給subscriptionFound
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
//EventBus發(fā)送一個沒人認(rèn)領(lǐng)的事件555。钾麸。更振。
post(new NoSubscriberEvent(this, event));
}
}
}
原來這個方法還不是真身炕桨,只是對消息類型進(jìn)行了繼承關(guān)系的判斷,并且獲取到消息發(fā)送結(jié)果殃饿,如果無訂閱者訂閱則發(fā)送一個特定事件谋作,那么再來看看postSingleEventForEventType
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
//同步將能接受到這個消息類型的訂閱者們?nèi)〕鰜? 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);
//消息是否被高優(yōu)先級的訂閱者中斷
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
//中斷的話這個消息類型后續(xù)訂閱者就接受不到了
break;
}
}
return true;
}
return false;
}
果然,EventBus按照這個類型的消息依次發(fā)送給訂閱者們乎芳,高優(yōu)先級的訂閱者甚至可以中斷低優(yōu)先級的訂閱者們遵蚜,那么我們來看消息是怎么調(diào)度給訂閱者的呢
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);
}
}
我們看到了四種類型
- POSTING:默認(rèn)線程環(huán)境,直接調(diào)用
invokeSubscriber
方法奈惑,表示在post
消息的線程環(huán)境直接執(zhí)行訂閱者的訂閱方法 - MAIN:如果當(dāng)前在主線程中吭净,那么直接執(zhí)行,否則就切到主線程去響應(yīng)事件
- BACKGROUND:如果當(dāng)前在工作線程中肴甸,那么直接執(zhí)行寂殉,否則就轉(zhuǎn)到工作線程響應(yīng)事件
- ASYNC:不管
post
線程情況,直接切到一個新線程中處理事件
事件響應(yīng)線程和優(yōu)先級0-100都可以在注解中配置
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
其中我們先來看看invokeSubscriber
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);
果然原在,直接通過反射直接調(diào)用了invoke
友扰,那么MAIN模式是怎么切換的呢,還是老樣子庶柿,我們從入口來
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
可以從命名猜測HandlerPoster
內(nèi)部維護(hù)了一個隊列
//EventBus調(diào)用的入口
void enqueue(Subscription subscription, Object event) {
//從后續(xù)隊列池中獲取一條
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
//再次校驗村怪,有點(diǎn)像DCL單例。
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
//反射執(zhí)行
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
//保證2條EventBus之間處理消息間隔不小于10s浮庐,防止主線程卡頓
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
看來最后涉及線程間調(diào)度后處理消息還有貓膩甚负,我們在PendingPost
里瞧瞧看
final class PendingPost {
//待處理的 事件對應(yīng)訂閱者的 請求池
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
Object event;
Subscription subscription;
PendingPost next;
private PendingPost(Object event, Subscription subscription) {
this.event = event;
this.subscription = subscription;
}
//
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
//同步在池中取出一個對象池
PendingPost pendingPost = pendingPostPool.remove(size - 1);
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
return new PendingPost(event, subscription);
}
static void releasePendingPost(PendingPost pendingPost) {
pendingPost.event = null;
pendingPost.subscription = null;
pendingPost.next = null;
synchronized (pendingPostPool) {
//池中增長速度不要過快,1000數(shù)量的閾值
if (pendingPostPool.size() < 10000) {
pendingPostPool.add(pendingPost);
}
}
}
}
這么一看好像就只是個對象池嘛审残,不知道有沒有分析錯梭域,敬請dalao指正,那么BACKGROUND
和ASYNC
我們也就快速過一遍好了搅轿,上文概要的提到這2個mode其實都是實現(xiàn)Runnable接口病涨,只不過BACKGROUND
內(nèi)部的run方法是同步單線程處理的,而ASYNC
是相當(dāng)于多線程的璧坟。
那么Eventbus的post事件我們已經(jīng)了解完了原理,無非就是遍歷所有該處理這個事件的訂閱者沸柔,然后根據(jù)方法的注解來分配到指定的線程環(huán)境中invoke
執(zhí)行循衰,那么發(fā)送粘性事件是在怎么樣的呢
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
//也是調(diào)用post
post(event);
}
其實本質(zhì)發(fā)送粘性事件和普通發(fā)送是一個原理,只不過追加了這條事件到粘性隊列中褐澎,而后來訂閱者如果要響應(yīng)之前發(fā)的粘性事件那么就應(yīng)該是在訂閱的時候会钝,就是在subscribe
方法下的checkPostStickyEventToSubscription
,方法內(nèi)部直接調(diào)用postToSubscription
。
那么EventBus的一套正常流程我們就基本分析完了(°?°)?其余還有一些EventBus的取消后續(xù)事件cancelEventDelivery
迁酸,移除粘性事件removeStickyEvent
先鱼,反注冊unregister
,都比較簡單奸鬓,這里就不分析了焙畔。
Plus
你們以為這就把EventBus源碼分析完了,當(dāng)然沒有串远!EventBus還有一個必殺技我們還沒分析宏多,綜上所述,EventBus對訂閱者信息多出用到了反射澡罚,這是在一定程度上消耗性能的伸但,那么EventBus的開源作者是怎么優(yōu)化的呢,沒錯就是通過預(yù)編譯技術(shù)來生成索引留搔,相像一下更胖,我們每次要查詢一個單詞就要從頭去字典找,如果現(xiàn)在有個字典的目錄隔显,我們不是就能很快的找到嗎却妨?
這張據(jù)說是開源作者博客里的性能對比圖,可以看到加了索引的EventBus性能簡直就是打了雞血括眠,蹭蹭蹭的上去彪标。
完整依賴如下
在項目根目錄gradle中導(dǎo)入apt編譯插件
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
Moudle處
apply plugin: 'com.neenbedankt.android-apt'
apt {
arguments {
//改成你的索引
eventBusIndex "com.tk.test.EventBusIndex"
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
編譯過后我們會在這個路徑下找到索引app>build>generated>source>apt>debug>com.tk.test.EventBusIndex
/** This class is generated by EventBus, do not edit. */
public class EventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(com.tk.test.activity.SplashActivity.class, true, new SubscriberMethodInfo[] {
//通過apt的方式直接把方法名等等參數(shù)全放這里了
new SubscriberMethodInfo("onEventReceive", String.class, ThreadMode.POSTING, 0, true),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
//實現(xiàn)方法,用來將指定訂閱者類型的相關(guān)信息提取出來
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
//初始化導(dǎo)入索引
EventBus.builder()
.addIndex(new EventBusIndex())
.installDefaultEventBus();
在EventBusIndex
只有一條記錄哺窄,因為筆者只有一個Activity作為訂閱者╮(╯▽╰)╭捐下,那么索引又是在哪里被讀取到EventBus的map中呢账锹,我們再回過頭來看看搜尋器的findSubscriberMethods
方法萌业,在里面有一個從索引中尋找的方法findUsingInfo
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//獲取索引
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
//遍歷索引
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
private SubscriberInfo getSubscriberInfo(SubscriberMethodFinder.FindState findState) {
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
//訂閱者有繼承關(guān)系
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
//遍歷索引
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
其實基本和這個方法findUsingReflection
雷同,然后從FindState的subscriberInfo
或者builder中的索引集合中取出來奸柬,然后遍歷索引再進(jìn)行篩選生年。后面的流程就一樣了
apt筆者不太懂,這里就拋磚引玉了廓奕,大抵就是編譯時把之前的搜尋器SubscriberMethodFinder
的掃描過程以另外一種模式又執(zhí)行了一遍抱婉,然后將結(jié)果生成了一個類,原理和大名鼎鼎的ButterKnife類似桌粉。
Ps:EventBus在開發(fā)中可以封裝成一個統(tǒng)一的抽象Event或者接口蒸绩,訂閱者們通過消息的key來區(qū)分;
如果對RxJava了解比較深入的同學(xué)可以用RxBus來代替铃肯,能節(jié)省一些代碼
這幾天學(xué)習(xí)源碼的過程有點(diǎn)累患亿,文章如有錯誤,敬請指正!
最后再上一張筆者整理的流程圖
耶穌說:“我就是道路步藕、真理惦界、生命;若不藉著我咙冗,沒有人能到父那里去沾歪。 (約翰福音 14:6 和合本)