EventBus
是基于觀察者模式的發(fā)布/訂閱事件總線侦铜,它讓組件間的通信變得更加簡(jiǎn)單。類似廣播系統(tǒng)钟鸵,不過 EventBus
所有的訂閱和發(fā)送都是在內(nèi)存層面的钉稍,使用起來遠(yuǎn)比廣播簡(jiǎn)單,也更容易管理棺耍。
先說明在事件總線中的幾個(gè)關(guān)鍵詞:
- 事件發(fā)送者贡未,發(fā)出事件的人
- 訂閱者,處理事件的人
- 訂閱者中處理事件的方法浆竭,因?yàn)槊總€(gè)訂閱者感興趣的事件有多種友题,因此會(huì)有多個(gè)處理事件的方法
- 訂閱第队,一個(gè)訂閱指的是某個(gè)訂閱者中的處理某個(gè)事件的方法偏竟,由訂閱者和事件類型唯一確定。
訂閱事件注冊(cè)
當(dāng)希望接受到事件時(shí)砌烁,需要在 onCreate()
執(zhí)行 register()
方法秒梳,在注冊(cè)方法中會(huì)檢索當(dāng)前類中聲明的接受事件的方法梆砸,并將他們注冊(cè)到對(duì)應(yīng)的映射中哺哼。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
內(nèi)存中存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)有如下幾個(gè):
// 事件 - List<訂閱(Subscription)> 每個(gè)訂閱由訂閱者、事件類型唯一確定
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// 訂閱者 - List<關(guān)注的事件> 每個(gè)訂閱者可能關(guān)注多個(gè)事件
private final Map<Object, List<Class<?>>> typesBySubscriber;
// 事件對(duì)應(yīng)下的粘滯事件
private final Map<Class<?>, Object> stickyEvents;
查找訂閱方法列表
當(dāng)執(zhí)行 register()
方法時(shí)叼风,會(huì)借助 SubscriberMethodFinder
類從注冊(cè)的對(duì)象的 Class
中查找取董。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 從緩存中找是否已經(jīng)檢索過了,有緩存就直接返回
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
// 是否忽略索引功能无宿,忽略的話會(huì)直接使用反射的方法搜索茵汰,否則會(huì)檢測(cè)有沒有相關(guān)的索引可以使用
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 支持索引的情況,會(huì)優(yōu)先從索引中查找孽鸡,加快查找的速度
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
// 沒有找到任何的訂閱方法將會(huì)拋出異常蹂午,所以至少要用注解訂閱一個(gè)方法
} else {
// 針對(duì)這個(gè) class 查找到訂閱的方法列表,存緩存彬碱,下次更快的返回
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
因?yàn)槲覀儾豢紤]索引的情況豆胸,最終查找方法都會(huì)走到方法 findUsingReflectionInSingleClass
,內(nèi)部的原理相對(duì)簡(jiǎn)單巷疼,遍歷該類的所有方法晚胡,找到共有的、只有一個(gè)參數(shù)嚼沿、且?guī)в?@Subscribe
注解的方法估盘,存儲(chǔ)到列表中。
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
methods = findState.clazz.getDeclaredMethods();
for (Method method : methods) {
int modifiers = method.getModifiers();
// 共有的方法 & 不是靜態(tài)骡尽、抽象遣妥、不是編譯生成的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
// 參數(shù)長(zhǎng)度只能是1
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
// 方法上面帶有 @Subscribe 注解
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()));
}
}
}
}
}
}
這個(gè)過程是一個(gè)循環(huán),每次都會(huì)向上查找當(dāng)前類的父類攀细,知道到達(dá) java
內(nèi)置的類中箫踩,這就意味著,父類中聲明的訂閱方法辨图,在子類實(shí)例中也會(huì)接收到班套。查找的結(jié)果最終會(huì)生成一個(gè) SubscriberMethod
的列表,這個(gè)類中存儲(chǔ)了訂閱方法的全部信息故河,數(shù)據(jù)結(jié)構(gòu)如下:
public class SubscriberMethod {
final Method method; // 當(dāng)前的方法吱韭,可執(zhí)行
final ThreadMode threadMode; // 線程類型
final Class<?> eventType; // 參數(shù)的類型,也就是他訂閱的事件的類型
final int priority; // 優(yōu)先級(jí)
final boolean sticky; // 是否是粘滯事件
String methodString; // 方法的字符串
}
訂閱到映射中
// 事件 - List<訂閱(Subscription)> 每個(gè)訂閱由訂閱者、事件類型唯一確定
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// 訂閱者 - List<關(guān)注的事件>
private final Map<Object, List<Class<?>>> typesBySubscriber;
訂閱的過程就是根據(jù)訂閱者 Subscriber
及該訂閱者的某個(gè)處理事件的方法 SubscriberMethod
來生成 Subscription
并且存儲(chǔ)到映射當(dāng)中理盆。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 存儲(chǔ)到 事件 - List<訂閱> 映射中
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); // ... 不存在則創(chuà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;
}
}
// 存儲(chǔ)到 訂閱者 - List<關(guān)注的事件> 映射中
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); // ... 不存在則創(chuàng)建新的
subscribedEvents.add(eventType);
// ...
// 對(duì) Sticky Event 的處理痘煤,后面單獨(dú)說
}
取消注冊(cè)
由于事件總線的機(jī)制基于內(nèi)存實(shí)現(xiàn),所有的訂閱都會(huì)存儲(chǔ)在內(nèi)存中猿规,因此必須在合適的時(shí)機(jī)取消注冊(cè)衷快,來釋放占用的內(nèi)存空間。
當(dāng)取消注冊(cè)時(shí):
- 借助之前存儲(chǔ)的
訂閱者-List<關(guān)注事件>
的映射快速的獲取到姨俩,當(dāng)前訂閱者感興趣的事件列表蘸拔。 - 然后遍歷事件列表,從
事件-List<訂閱>
的映射中环葵,刪除所有的訂閱调窍。 - 最后將當(dāng)前訂閱者從
訂閱者-List<關(guān)注事件>
刪除,完成取消訂閱的過程张遭。
獲取當(dāng)前訂閱者關(guān)注的全部事件邓萨,遍歷取消注冊(cè)。
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
}
}
// 從訂閱列表中刪除對(duì)應(yīng)的訂閱
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
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--;
}
}
}
}
發(fā)送事件
當(dāng)需要發(fā)送事件使用 EventBus
的 post()
方法菊卷。
借助 ThreadLocal
每個(gè)線程單獨(dú)維護(hù)一個(gè)缔恳、且僅一個(gè) PostingThreadState
對(duì)象,這個(gè)對(duì)象的數(shù)據(jù)結(jié)構(gòu)如下, 內(nèi)部存儲(chǔ)了當(dāng)前發(fā)送事件狀態(tài)的的一些關(guān)鍵信息洁闰。
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>(); // 事件隊(duì)列
boolean isPosting; // 是否正在發(fā)送事件歉甚,是的話不需要啟動(dòng)循環(huán)讀取事件
boolean isMainThread; // 是否是主線程
Subscription subscription; // 一個(gè)訂閱
Object event; // 當(dāng)前的事件
boolean canceled; // 是否被取消
}
獲取本線程的 PostingThreadState 對(duì)象,進(jìn)行初始化渴庆,并開始輪詢處理隊(duì)列中的事件铃芦。
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
try {
// 從隊(duì)列中循環(huán)讀取事件處理
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
繼續(xù)往深里面看 postSingleEvent()
方法,他每次處理一個(gè)從隊(duì)列中取出來的事件襟雷,這里做了一個(gè)區(qū)分刃滓,是否支持繼承,這個(gè)值默認(rèn)是 true
耸弄,支持繼承時(shí)咧虎,如果對(duì)當(dāng)前事件的父類、接口對(duì)應(yīng)的事件感興趣计呈,那么他也可以處理該事件砰诵。例如當(dāng)前要處理 A 事件,A 繼承自 B捌显,同時(shí)實(shí)現(xiàn) C 接口茁彭,能處理 B,C 事件的訂閱者將也會(huì)參與處理此 A 事件。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
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);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
// 沒有找到訂閱的方法,處理分支
}
}
事件訂閱者排隊(duì)處理
接下來會(huì)走 postSingleEventForEventType()
方法,這個(gè)方法負(fù)責(zé)找到對(duì)這個(gè)事件感興趣的 訂閱 Subscription 列表妹萨, Subscription
里面包含了訂閱者年枕、處理對(duì)應(yīng)事件的方法等信息。
拿到列表之后便循環(huán)將事件給列表中的訂閱依次處理乎完,在之前注冊(cè)時(shí)熏兄,是有一個(gè)優(yōu)先級(jí)別的,優(yōu)先級(jí)高的將會(huì)先獲得處理事件的權(quán)利树姨。
優(yōu)先級(jí)別較高的處理者可以停止事件的傳遞摩桶,只需要拋出一個(gè)異常,被 finally
塊捕捉后帽揪,就會(huì)中斷輪詢典格,從而終止事件的傳遞。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?>
CopyOnWriteArrayList<Subscription> subscriptions;
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 {
// 讓 subscription 處理 event
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
// 如果優(yōu)先級(jí)別較高的處理者異常,則后續(xù)處理者將無法處理該事件
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
// 退出輪詢
if (aborted) {
break;
}
}
return true;
}
return false;
}
分發(fā)線程處理者執(zhí)行
處理事件的最后一步砾肺,是 postToSubscription()
他負(fù)責(zé)將事件的處理分發(fā)到不同的線程隊(duì)列中挽霉,在添加訂閱注解 @Subscribe
時(shí)可以指定 threadMode
,這極大的方便了我們?cè)谑录鬟f后切換不同線程處理事件变汪,例如我們常常要在子線程處理數(shù)據(jù)侠坎,而通知主線程更新 UI
,使用 EventBus
只需要指定 @Subscribe(threadMode=ThreadMode.Main)
則在處理事件時(shí)所有操作在內(nèi)部便被切換到了主線程裙盾,真正做到了對(duì)線程切換的無感知实胸。
分為了如下幾種類型:
-
POSTING
發(fā)送線程,或者說是當(dāng)前線程更貼切一些番官,在其他類庫中通常叫Immediate
庐完, 也就是不用切換線程。 -
MAIN
主線程徘熔,不解釋门躯。 -
BACKGROUND
后臺(tái)線程,如果發(fā)送線程是主線程酷师,則開辟新的線程執(zhí)行讶凉,否則將在當(dāng)前線程執(zhí)行。 -
ASYNC
異步線程山孔,無論怎樣懂讯,總是開啟新的子線程去執(zhí)行。
這里就要看一下幾個(gè)處理者 HandlerPoster
/BackgroundPoster
/AsyncPoster
實(shí)現(xiàn)原理大致相同台颠,內(nèi)部維護(hù)一個(gè)隊(duì)列褐望,不停的把里面的事件取出來處理。
-
HandlerPoster
是基于Handler
實(shí)現(xiàn)對(duì)隊(duì)列的輪詢。 -
BackgroundPoster
則是用死循環(huán)來做的譬挚,誰讓人家有自己的線程呢锅铅。 -
AsyncPoster
就更富了,根本不輪詢减宣,每次都是一個(gè)新的線程盐须。
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;
}
}
最終調(diào)用的 invokeSubscriber()
很簡(jiǎn)單就是利用反射調(diào)一下對(duì)應(yīng)的 method
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
粘滯事件的實(shí)現(xiàn)
我把 Sticky Event
翻譯成 粘滯事件 不知道對(duì)不對(duì),他的出現(xiàn)主要是因?yàn)槲覀冃枰幚硎录强偸且茸?cè)再發(fā)送事件漆腌,根本原因在于當(dāng)一個(gè)事件發(fā)出時(shí)贼邓,他的生命周期很短,所有對(duì)他感興趣的訂閱者處理完了之后他就被拋棄了闷尿,后面的訂閱者再感興趣也沒用塑径,因?yàn)樵缇捅磺謇砝病?/p>
要解決這個(gè)問題也很簡(jiǎn)單,就是延長(zhǎng)事件的生命周期填具,即使大家都不理他了统舀,他也能頑強(qiáng)的活著,萬一后面還有人對(duì)他感興趣呢劳景。所以實(shí)現(xiàn)的原理也就很明了了誉简,找個(gè)列表把它全部存起來,除非你手動(dòng)給刪除盟广,否則就 粘不拉幾 的附著在你的內(nèi)存里闷串,等著他的真命天子出現(xiàn)。
// 事件類型 - 事件實(shí)例
private final Map<Class<?>, Object> stickyEvents;
// 發(fā)送粘滯事件時(shí)筋量,先存起來給后面的人用烹吵,然后按照常規(guī)流發(fā)送出去
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
post(event);
}
還要提供一個(gè)渠道,讓新加入進(jìn)來的訂閱者能夠察覺到這里有粘滯事件的存在桨武,如果感興趣也可以處理它肋拔。這個(gè)時(shí)機(jī)就是注冊(cè)時(shí),當(dāng)一個(gè)訂閱者被添加到注冊(cè)表中時(shí)玻募,此時(shí)如果存在粘滯事件只损,用當(dāng)前訂閱者感興趣的事件為 key
獲取存在的粘滯事件,如果有感興趣的就臨幸一下七咧。于是可以完善一下之前未說完的 register()
方法:
- 首先要求當(dāng)前訂閱者的處理事件的方法要對(duì)粘滯事件感興趣跃惫,這個(gè)在注解上可以聲明。
- 繼承艾栋,如果支持繼承爆存,當(dāng)前事件的子類粘滯事件都會(huì)被取出來檢查是否可以被處理。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// ... 前面這塊說過了
// 這個(gè)訂閱者的這個(gè)訂閱方法是對(duì)粘滯事件感興趣的
if (subscriberMethod.sticky) {
// 事件是否繼承
if (eventInheritance) {
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
// 當(dāng)前事件的子類粘滯事件都會(huì)被取出來檢查是否可以被處理
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);
}
}
}
接下來的 checkPostStickyEventToSubscription()
就會(huì)調(diào)用前面已經(jīng)說過的 postToSubscription()
方法蝗砾,開始發(fā)送到不同的線程中執(zhí)行先较,這部分和普通的事件是一樣的啦携冤。
理解事件的繼承
粘滯事件這里也出現(xiàn)了一個(gè)關(guān)于事件繼承的檢索,在上一節(jié)也出現(xiàn)了一次闲勺,單獨(dú)拿出來說一下異同之處曾棕。
可以類比函數(shù)入?yún)⒌南拗疲绻粋€(gè)方法聲明中參數(shù)是父類菜循,那么傳參時(shí)可以傳遞子類對(duì)象進(jìn)去翘地,聲明了子類的話,是不能傳遞父類對(duì)象的癌幕。
舉個(gè)例子衙耕,設(shè)定下場(chǎng)景,我們現(xiàn)在有事件基類 BaseEvent
和一個(gè)事件子類 ImplEvent
是繼承關(guān)系勺远。
第一種場(chǎng)景橙喘,發(fā)送普通事件,我發(fā)送了一個(gè) ImplEvent
胶逢,因?yàn)槲野l(fā)的是個(gè)子類事件厅瞎,也就是說所有聲明關(guān)注 BaseEvent
的訂閱者也都可以將當(dāng)前事件作為入?yún)ⅲ韵蛏蠙z索對(duì) ImplEvent
父類初坠、父接口感興趣的訂閱者去執(zhí)行磁奖。
第二個(gè)場(chǎng)景,發(fā)送粘滯事件某筐,發(fā)送一個(gè) BaseEvent
的粘滯事件,因?yàn)槭窃谧?cè)時(shí)觸發(fā)執(zhí)行冠跷,那么說明當(dāng)前訂閱者對(duì) BaseEvent
感興趣南誊,既然他的入?yún)⑹歉割愂录敲醋宇愂录餐瑯涌梢宰鳛樗奶幚硎录椒ǖ娜雲(yún)⒚弁校谑菣z索所有粘滯事件找到所有 BaseEvent
的子類事件都交給當(dāng)前訂閱者處理抄囚。
Weex 事件機(jī)制
在 Weex
中有一個(gè) BroadcastChannel
的 API
用來實(shí)現(xiàn)頁面間的通信,在原生部分使用 WebSocketModule
實(shí)現(xiàn)橄务,不過經(jīng)過實(shí)驗(yàn)發(fā)現(xiàn)幔托,注冊(cè)和發(fā)送沒有什么大問題,不過在取消注冊(cè)這塊做的有漏洞蜂挪,出現(xiàn)多次頁面銷毀但是無法取消對(duì)事件監(jiān)聽的情況(可能是當(dāng)時(shí)嘗試的時(shí)候版本低一些)重挑,主要是因?yàn)?module
的生命周期沒能和 weex
頁面實(shí)例更好的綁定起來,而且它是基于 W3C
的標(biāo)準(zhǔn)設(shè)計(jì)的棠涮,也沒有實(shí)現(xiàn)類似粘滯事件這種功能的支持谬哀。
最后決定根據(jù)事件總線的機(jī)制來嘗試實(shí)現(xiàn)頁面之間的通信,在 Weex
中有一個(gè) 頁面內(nèi) 通信的接口严肪,他是 native
和 weex
通信的通道史煎,可以用一個(gè) key
作為標(biāo)示符谦屑,觸發(fā)當(dāng)前 weex
頁面中對(duì) key
事件感興趣的的方法,關(guān)于 weex
相關(guān)的內(nèi)容這里不細(xì)說篇梭。
((WXSDKInstance)instance).fireGlobalEventCallback(key, params)
實(shí)現(xiàn)原理類似 EventBus
氢橙,不過因?yàn)榛?weex
就沒那么復(fù)雜,同樣需要維護(hù)一個(gè)注冊(cè)表恬偷,相對(duì)于 EventBus
要對(duì)訂閱者強(qiáng)引用持有悍手,這里使用了每個(gè) weex
頁面唯一的 instanceId
作為標(biāo)記,存儲(chǔ)這個(gè)標(biāo)記而不是存儲(chǔ)真正的 WXSDKInstance
對(duì)象喉磁,避免內(nèi)存泄漏谓苟。
private val mEventInstanceIdMap by lazy { mutableMapOf<String, MutableSet<String>>() }
注冊(cè),當(dāng) weex
那邊發(fā)起注冊(cè)時(shí)协怒,拿到對(duì)應(yīng)的 instanceId
存儲(chǔ)到映射中涝焙。
// 注冊(cè)接受某事件
// event.registerEvent('myEvent')
// globalEvent.addEventListener('myEvent', (params) => {});
fun registerEvent(key: String?, instantId: String?) {
// do check...
val nonNullKey = key ?: return
val registerInstantIds = mEventInstanceIdMap[nonNullKey] ?: mutableSetOf()
registerInstantIds.add(instantId)
mEventInstanceIdMap[nonNullKey] = registerInstantIds
}
發(fā)送事件時(shí),根據(jù)事件的 key
拿到對(duì)他關(guān)注的訂閱者的 instanceId
列表孕暇,循環(huán)從 weex sdk
中取出真正的 WXSDKInstance
對(duì)象仑撞,再利用頁面內(nèi)通信的 API
將事件發(fā)送給指定頁面,達(dá)到頁面間通信的目的妖滔。
// 發(fā)送事件
// event.post('myEvent',{isOk:true});
fun postEvent(key: String, params: Map<String, Any>) {
// do check...
val registerInstantIds = mEventInstanceIdMap[key] ?: listOf<String>()
val allInstants = renderManager.allInstances
for (instance in allInstants) {
// 遍歷找到訂閱的 instanceId 進(jìn)而拿到 weex 實(shí)例發(fā)送頁面內(nèi)事件
if (instance != null
&& !instance.instanceId.isNullOrEmpty()
&& registerInstantIds.contains(instance.instanceId)) {
instance.fireGlobalEventCallback(key, params)
}
}
}
當(dāng)頁面銷毀時(shí)隧哮,同時(shí)自動(dòng)取消注冊(cè),釋放內(nèi)存和避免不必要的事件觸發(fā)
override fun onWxInstRelease(weexPage: WeexPage?, instance: WXSDKInstance?) {
val nonNullId = instance?.instanceId ?: return
for (mutableEntry in mEventInstanceIdMap) {
if (mutableEntry.value.isNotEmpty()) {
mutableEntry.value.remove(nonNullId)
}
}
}
最后座舍,目前只是一個(gè)簡(jiǎn)單的實(shí)現(xiàn)沮翔,能夠基本實(shí)現(xiàn)頁面間通信的需求,不過還需要更多地調(diào)研和其他端同學(xué)的配合曲秉,相信會(huì)越來越完善采蚀。
目前維護(hù)的幾個(gè)項(xiàng)目,求 ????