前言
本來想學習下EventBus最新框架的源碼,但是最新的框架的代碼量已經很大了,很多都是錦上添花的東西,很多核心的原理代碼需要從中剝離出來去了解。但是對於剛開始看源碼就直接拿到這麼功能豐富並完善的代碼,可能收效甚微。爲了自己學習並且幫助讀者同志們學習,這裏自己根據以前學習的經驗理解,手寫一份簡化Eventbus源碼,和大家一起學習此框架的要義。
EventBus技術架構
簡介
Eventbus的存在是爲了解決組件間的簡單通信問題,採用發佈-訂閱者模式,通過統一的管理機構(bus總線)維護所有的訂閱者,將發佈者發佈的消息發送對應訂閱者的處理方法處理。
發佈者和訂閱者可以是任意Activity、Fragment、Service、Thread等,只要在一個進程中就可以,其不支持多進程。目前git地址爲:https://github.com/greenrobot/EventBus。截至今日,最新版本3.2.0。
使用方法:
EventBus in 3 steps
// 1.Define events: 定義Event,這裏爲用戶使用時自定義的部分,框架不包含此格式,爲任意java bean包含自己需要傳輸的信息。
public static class MessageEvent { /* Additional fields if needed */ }
//2 Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
//準備訂閱者方法,方法名是任意的,只是必須帶@Subscribe註解,必須只有一個參數,參數需是第一步發送的消息。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
//3.Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:
// 註冊和解註冊eventbus,可以在activity和fragment、或者線程中,根據場景需要的時機進行註冊解註冊。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
//4.Post events: 發送event,可以是任何地方
EventBus.getDefault().post(new MessageEvent());
架構原理
此架構圖爲EventBus原版的架構圖。
中間的EventBus是個大管家,負責所有消息流轉和註冊信息保存,程序運行起來時,註冊者如Activity調用了register方法並把自己的this作爲參數傳入,大管家會登記所有的登記者和接收處理方法(@Subscribe註解方法)。當有發佈者發佈消息,即調用了EventBus.getDefault().post方法,將參數自定義Event發佈到管家EventBus中, 大管家找到這個進程大家庭中所有註冊了此Event類型的方法,並進行調用處理。
手寫框架
模塊
要手寫框架,即需要包括框架核心原理和實現核心方法。這裏進行列舉,以明確程序框架。根據上節EventBus簡介的使用方法可知需要實現:
- 自定義註解@Subscribe和線程類型枚舉。
- EventBus類,並實現註冊register方法、解註冊方法unregister、post方法,且有getDefault單例方法。
- 獲取所有註冊類中的加了@Subscribe註解的方法、方法參數和註解信息(線程和優先級信息)。
- 實現post邏輯,即將發送Event到對應訂閱方法參數是Event類型的方法
源碼設計
菜譜有了,那就可以開始做菜了。如果想同時看着源碼看文章的話,可以從github查看Demo。https://github.com/qingdaofu1/ZephyrBus
自定義註解和創建ThreadMode枚舉類
自定義註解可以添加默認值,並且這裏標註此註解是使用在方法上,並且是運行時保留策略,關於註解的詳細原理可以參照我的另一篇總結:Android註解-看這篇文章就夠了
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
int priority() default 0;
}
線程模型肯定包括默認值POSTING,即和發送線程保持一致。MAIN線程即主線程,BACKGROUND即後臺線程,非主線程。這裏僅作示例,可以學習EventBus增加其他選項進行串行隊列的處理或者線程池多線程等特性。
public enum ThreadMode {
POSTING,
MAIN,
BACKGROUD
}
創建MyEventBus類和必要方法
- 數據結構
爲了保存訂閱者的處理方法需要兩個數據結構。
由於用戶不一定在一個地方註冊,即如果進程中Activity中、Fragment、Service等都有註冊,則需要一個結合記錄每個註冊類中所有的訂閱方法,這個集合即typesBySubscriber 。
然後比如用戶有多種Event類型,比如Aevent、Bevent…,需要有個能記錄所有處理Aevent的訂閱者方法類和所有處理Bevent的方法類。如果有更多則記錄更多。這個數據結構即subscriptionsByEventType ;其中的CopyOnWriteArrayList數據結構用法可以參考:CopyOnWriteArrayList的原理及使用
這裏學習了源碼中的數據結構名,以便讀者返回讀源碼時更容易理解。
/**
* 同一類型EventType類與所有註冊方法的集合
*/
private Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType = new HashMap<>();
/**
* 所有類有註冊MyEventBus的 類與其內部所有處理方法集合。
*/
private static final Map<Object, List<Class<?>>> typesBySubscriber = new HashMap<>();
//訂閱處理方法收集和調用策略抽象類,目前只實現了反射策略,另增加了APT類,待後邊完善
MethodInvokeStrategy invokeStrategy;
MethodInvokeStrategy爲抽象方法類,定義了收集訂閱方法和調用方法的接口,並創建了主線程的Handler和工作線程Handler供調用時使用。
public abstract class MethodInvokeStrategy {
private static final String TAG = "MethodInvokeStrategy";
private static HandlerThread handlerThread = new HandlerThread("workThread");
protected static Handler mainHandler;
protected static Handler workHander;
public MethodInvokeStrategy() {
handlerThread.start();
mainHandler = new Handler(Looper.getMainLooper());
workHander = new Handler(handlerThread.getLooper());
}
public List<SubscribedMethod> getAllSubscribedMethods(Object subscriber) {
return null;
}
public void invokeMethod(Subscription subscription, Object event) {
}
這裏的必要方法包括單例方法getDefault、註冊方法register、解註冊方法unregister、發送方法post。
- getDefault方法單例
這是較常用寫法,如果想寫不一樣的單例可以參考:
設計模式之單例模式的幾種寫法
/**
* 單例
* * @return
*/
public static MyEventBus getDefault() {
if (instance == null) {
synchronized (MyEventBus.class) {
if (instance == null) {
instance = new MyEventBus();
}
}
}
return instance;
}
- register方法
根據上邊描述的,爲了能把發佈者的Event消息發佈到所有的訂閱者,需要記錄所有處理Event的消息方法類信息。這裏包含實際的處理方法,最終返回上一步的存儲集合。
由於register將當前類的this傳入,即相當於獲取了這個類的實例,即能夠拿到其內的所有方法參數等信息。這些都是供實際收集方法的類需要的。
/**
* 註冊subscriber到MyEventBus,並獲取其所有加了{@link Subscribe} 的方法,並放入集合中
*
* @param subscriber 訂閱者類,即通過register將this參數傳過來的類,可以是activity、service、fragment、thread等。
*/
public void register(Object subscriber) {
List<SubscribedMethod> allSubscribedMethods = invokeStrategy.getAllSubscribedMethods(subscriber);
for (SubscribedMethod subscribedMethod : allSubscribedMethods) {
Class<?> eventType = subscribedMethod.getEventType();
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
}
// TODO: 2020/4/18 如果做priority邏輯,則在此處需要排序添加
subscriptions.add(new Subscription(subscriber, subscribedMethod));
// 獲取這個訂閱者類中記錄的所有的eventType類型
List<Class<?>> eventTypesInSubscriber = typesBySubscriber.get(subscriber);
if (eventTypesInSubscriber == null) {
eventTypesInSubscriber = new ArrayList<>();
typesBySubscriber.put(subscriber, eventTypesInSubscriber);
}
eventTypesInSubscriber.add(eventType);
}
printTypesBySubscriber(typesBySubscriber, subscriber);
}
- unregister方法
解註冊就比較簡單了,直接將集合中對應的註冊類的key去除。
/**
* 解註冊eventbus
*
* @param subscriber
*/
public void unregister(Object subscriber) {
if (typesBySubscriber != null) {
typesBySubscriber.remove(subscriber);
}
}
post方法
post方法將參數event發送,發送時根據eventbus中大管家保存的所有能處理此event的方法發送過去處理。
/**
* 發送event消息到訂閱者 處理方法
*
* @param event
*/
public void post(Object event) {
if (subscriptionsByEventType.size() <= 0) {
Log.e(TAG, "post: no any eventbus registed named" + event.toString());
return;
}
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(event.getClass());
for (Subscription subscription : subscriptions) {
invokeStrategy.invokeMethod(subscription, event);
}
}
到這裏基本所有的方法都定義好了,理解上我們可以把整個EventBus的架構原理能夠理解透了。但是實際的反射策略還沒有講。
實現獲取所有訂閱者處理方法,這裏既然是反射實現的,自然需要懂反射的原理,這裏我在寫的時候也是很多的不熟悉,後邊會跟上一篇介紹反射的文章,聽別人講或者看別人文章不如自己去實現下,你會發現寫的時候好像不是那麼回事,還是有些不理解的。
總之核心思想就是,通過註冊類,可以獲取所有的方法,然後獲取方法的註解,獲取註解的值,獲取帶特定註解方法的Event類,這些都是大管家需要收集的資料。
/**
* 獲取這個訂閱者類中所有的帶{@link Subscribe}的方法
*
* @param subscriber 訂閱者類,即通過register將this參數傳過來的類,可以是activity、service、fragment、thread等。
*/
@Override
public List<SubscribedMethod> getAllSubscribedMethods(Object subscriber) {
//記錄訂閱者方法參數
//CopyOnWriteArrayList<Subscription> subscribedMethods = new CopyOnWriteArrayList<Subscription>();
List<SubscribedMethod> subscribedMethods = new ArrayList<>();
Class<?> aClass = subscriber.getClass();
//獲取所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
if (parameterTypes == null || parameterTypes.length > 1) {
throw new IllegalArgumentException("參數不能爲空,且只能有一個參數");
}
Class<?> parameterType = parameterTypes[0];
Log.d(TAG, "getAllSubscribedMethods: parameterType=" + parameterType.getName());
Subscribe annotation = declaredMethod.getAnnotation(Subscribe.class);
int priority = annotation.priority();
ThreadMode threadMode = annotation.threadMode();
SubscribedMethod subscribedMethod = new SubscribedMethod(declaredMethod, parameterType, threadMode, priority);
Log.d(TAG, "getAllSubscribedMethods: subscribedMethod=" + subscribedMethod.toString());
subscribedMethods.add(subscribedMethod);
}
}
return subscribedMethods;
}
最後便是根據線程模型調用方法的實現了,這裏首先想想上邊說的抽象類的兩個handler,有主線程的和工作線程的handler。這裏可以根據註解的值ThreadMode進行對應處理。相信大家對Handler也比較認識,如果需要進一步理解Handler和線程的關係,可以查看:Handler通信機制源碼解讀
public void invokeMethod(Subscription subscription, final Object event) {
final Object subscriber = subscription.getSubscriber();
SubscribedMethod subscribedMethod = subscription.getSubscribedMethod();
final Method method = subscribedMethod.getMethod();
switch (subscribedMethod.getThreadMode()){
case POSTING:
Log.d(TAG, "invokeMethod: ThreadMode=POSTING");
invoke(method, subscriber, event);
break;
case MAIN:
Log.d(TAG, "invokeMethod: ThreadMode=MAIN");
mainHandler.post(new Runnable() {
@Override
public void run() {
invoke(method, subscriber, event);
}
});
break;
case BACKGROUD:
Log.d(TAG, "invokeMethod: ThreadMode=BACKGROUND");
workHander.post(new Runnable() {
@Override
public void run() {
invoke(method, subscriber, event);
}
});
break;
}
}
private void invoke(Method method, Object subscriber, Object event){
try {
method.invoke(subscriber, event);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
源碼測試
首先在測試module中添加發送Event源碼,btn_send1爲主線程發送,btn_send2會從主線程和線程中交替發送。而處理端則在註解Subscribe中分別使用默認線程和主線程。
findViewById(R.id.btn_send1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyEventBus.getDefault().post(new WorkEvent(5));
}
});
findViewById(R.id.btn_send2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isThreadMain) {
MyEventBus.getDefault().post(new ViewEvent("主線程測試文字"));
} else {
new Thread() {
@Override
public void run() {
super.run();
MyEventBus.getDefault().post(new ViewEvent("子線程測試文字"));
}
}.start();
}
isThreadMain = !isThreadMain;
}
});
@Subscribe()
public void onEvent(final WorkEvent event) {
//...省略
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleView(final ViewEvent event) {
//...省略
});
}
測試結果:
本Demo的github地址爲:https://github.com/qingdaofu1/ZephyrBus,希望有需要的同學可以下載下來實操下加深理解。
下一步計劃是要增加apt模式的策略,有需要的可以繼續關注。
紙上得來終覺淺,絕知此事要躬行。
喜歡的可以點個贊鼓勵下,有問題可以留言溝通。