同步發表于http://avenwu.net/ioc/2015/01/29/custom_eventbus
Fork on github https://github.com/avenwu/support
Android有廣播和Receiver可以處理消息的傳遞和響應,要進行消息-釋出-訂閱,除此之外作為開發者現在也有其他類似的方案可以選擇,比如EventBus和Otto,都是比較熱門的三方庫。那麼這些三方庫到底是怎麼實作子產品之間的解耦,使得消息可以再不同的系統元件之間傳遞呢?
源碼剖析
由于是開源的,完全可以通過分析源代碼來了解這些個消息-釋出-訂閱方案在Android内是怎麼實作的,下面分别針對EventBus, Otto,Guava簡單分析。
EventBus v2.4.0
從github上擷取最新的EventBus代碼
git clone [email protected]:greenrobot/EventBus.git
直接找到EventBus這個類,從使用角度開始分析:
EventBus的使用可以參考項目wiki,簡單的來說就是EventBus#register(),EventBus#post(),實作onEventXXX
- EventBus利用反射技術
- register時周遊訂閱者,通過反射擷取所有public void onEventXXX(XX)方法,構造對應的訂閱執行個體,友善後期post時invoke方法【SubscriberMethodFinder】
- post後通過EventBus#postToSubscription分發至對應線程的事件控制器
Otto v1.3.6
Otto是大名鼎鼎的Square開發的
git clone [email protected]:square/otto.git
- Otto利用反射和注解
- register時周遊訂閱者内的所有方法,根據Subscribe和Produce注解獲得所有目标方法,@Subscribe public void xxx(YYY);訂閱方法必須是public且隻有一個參數
- post後在EventHandler#handleEvent内invoke事件,這裡沒有EventBus中的線程區分,預設是MainThread,也可以任何線程ANY,但是必須是一緻的ThreadEnforcer
從代碼上來看EventBus和Otto非常像,不知道EventBus作者在設計編碼時是否參考了Otto得設計,Otto項目則明确表示其是基于Google的Guava而來,Guava是Google開發的一個工具類庫包含了非常多的實用工具類,其中就有一個EventBus子產品,但是這個EventBus是并沒有針對Android平台做線程方面的考量。
是以三者的是有關聯的:
- Guava EventBus首先實作了一個基于釋出訂閱的消息類庫,預設以注解來查找訂閱者
- Otto借鑒Guava EventBus,針對Android平台做了修改,預設以注解來查找訂閱、生産者
- EventBus和前兩個都很像,v2.4後基于反射的命名約定查找訂閱者,根據其自己的說法,效率上優于Otto,當然我們測試過,這也不是本文的重點。
由于源代碼也不少,是以隻列舉了核心代碼對應的位置,感興趣的童鞋肯定會自己去研讀。
自定義一個EventBus
上面的庫當然不是為了研究而研究,現在了解了他們的核心思路後,我們其實已經可以着手自己寫一個簡單版的消息-釋出-訂閱。
現在先定義要實作的程度:
- 基于UI線程的消息-釋出-訂閱
- 使用上合EventBus盡量保持一緻,比如register,post,onEvent
思路設計
一個簡單的Bus大緻上需要有幾個東西,Bus消息中心,負責綁定/解綁,釋出/訂閱;Finder查找定義好的消息處理方法;PostHandler分發消息并處理.
Bus實作
這裡的Bus做成單例,這樣無論在什麼地方注冊,釋出都是有這個消息中心來處理。
用一個Map來儲存我們的訂閱關系,當消息到達時從map中取出該消息類型的所有訂閱方法,通過反射依次invoke。
public class Bus {
static volatile Bus sInstance;
Finder mFinder;
Map<Class<?>, CopyOnWriteArrayList<Subscriber>> mSubscriberMap;
PostHandler mPostHandler;
private Bus() {
mFinder = new NameBasedFinder();
mSubscriberMap = new HashMap<>();
mPostHandler = new PostHandler(Looper.getMainLooper(), this);
}
public static Bus getDefault() {
if (sInstance == null) {
synchronized (Bus.class) {
if (sInstance == null) {
sInstance = new Bus();
}
}
}
return sInstance;
}
public void register(Object subscriber) {
List<Method> methods = mFinder.findSubscriber(subscriber.getClass());
if (methods == null || methods.size() < 1) {
return;
}
CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.get(subscriber.getClass());
if (subscribers == null) {
subscribers = new CopyOnWriteArrayList<>();
mSubscriberMap.put(methods.get(0).getParameterTypes()[0], subscribers);
}
for (Method method : methods) {
Subscriber newSubscriber = new Subscriber(subscriber, method);
subscribers.add(newSubscriber);
}
}
public void unregister(Object subscriber) {
CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.remove(subscriber.getClass());
if (subscribers != null) {
for (Subscriber s : subscribers) {
s.mMethod = null;
s.mSubscriber = null;
}
}
}
public void post(Object event) {
//TODO post with handler
mPostHandler.enqueue(event);
}
}
Finder
查找訂閱方法即可以用注解,也可以用命名約定,這裡先實作命名約定的方式。
為了處理友善這裡和EventBus不完全一緻,隻做了方法名和參數的限制,但是最好實作的嚴謹些。
public class NameBasedFinder implements Finder {
@Override
public List<Method> findSubscriber(Class<?> subscriber) {
List<Method> methods = new ArrayList<>();
for (Method method : subscriber.getDeclaredMethods()) {
if (method.getName().startsWith("onEvent") && method.getParameterTypes().length == 1) {
methods.add(method);
Log.d("findSubscriber", "add method:" + method.getName());
}
}
return methods;
}
}
PostHandler
分發消息肯定要用到Handler,EventBus中自己維護了一個隊列來來處理消息的入棧、出棧,我這裡就世界用了Message來傳遞
public class PostHandler extends Handler {
final Bus mBus;
public PostHandler(Looper looper, Bus bus) {
super(looper);
mBus = bus;
}
@Override
public void handleMessage(Message msg) {
CopyOnWriteArrayList<Subscriber> subscribers = mBus.mSubscriberMap.get(msg.obj.getClass());
for (Subscriber subscriber : subscribers) {
subscriber.mMethod.setAccessible(true);
try {
subscriber.mMethod.invoke(subscriber.mSubscriber, msg.obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
void enqueue(Object event) {
Message message = obtainMessage();
message.obj = event;
sendMessage(message);
}
}
小結
基本上的代碼都在這裡,實作一個Bus還是挺簡單的,當然如果吧各種情況都考慮進去就會變得複雜一些,比如支援多線程線程,也不可能想本文這樣區區數百行代碼就搞定。
感興趣的可以到這裡擷取上面自定義bus的源代碼:https://github.com/avenwu/support/tree/master/support/src/main/java/net/avenwu/support
作者:小文字
出處:http://www.cnblogs.com/avenwu/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.