大名鼎鼎的EventBus很多人一定都用過,這個框架通過利用注解+反射,很好的實現(xiàn)了事件訂閱者與發(fā)布者的解耦。今天我們就手動實現(xiàn)一個簡易版本的EventBus。
先寫我們的EventBus類,模仿真正的EventBus類,我們這個類里面包含的也是標(biāo)準(zhǔn)的幾個方法:
1.void register(Object subscriber)
2.void unregister(Object subscriber)
3.void post(Object eventType)
EventBus里的方法只有這幾個(因為我們是簡易版本的嘛)。當(dāng)然我們的EventBus是一個單例模式類,我用的是雙重檢測null的那種:
private static EventBus defaultInstance;
public static EventBus getDefault(){
if(defaultInstance==null){
synchronized (EventBus.class){
if(defaultInstance==null) defaultInstance=new EventBus();
}
}
return defaultInstance;
}
接下來是一個很重要的成員變量:
private HashMap<Class<?>,ArrayList<Subscription>> subscriptionsByEventType=new HashMap<>();
解釋一下:HashMap的key為Class對象,其實就是訂閱者中各種onEvent*函數(shù)的參數(shù)的Class對象,用過EventBus的都知道我們可以利用不同的參數(shù)對象實現(xiàn)不同事件的訂閱。而HashMap的value是一個ArrayList,其中就是存儲針對不同的參數(shù)對象的訂閱。利用這個HashMap,可以輕易地找到所有訂閱某一參數(shù)類型事件的所有訂閱者。訂閱信息就包含在類Subscription中。
我們分析一下Subscription類中應(yīng)該包含哪些信息:
1.事件的訂閱者,即subscriber。
2.訂閱的事件,即method。
3.事件應(yīng)該發(fā)生的線程,即ThreadMode。
綜上,我們的類Subscription如下:
public class Subscription {
public Method method;
public Object subscriber;
public ThreadMode mode;
public Subscription(Method method, Object subscriber, ThreadMode mode) {
this.method = method;
this.subscriber = subscriber;
this.mode = mode;
}
}
其中ThreadMode就是一個簡單的枚舉,作為演示我只設(shè)置了兩個值:
public enum ThreadMode {
MainThread,PostThread
}
即post事件所在的線程以及主線程main。
EventBus的成員變量暫時就這些,還有一個稍后再講,接下來就是成員方法的實現(xiàn),我們一個一個來,首先是register方法。先看一下register的全部代碼:
public void register(Object subscriber){
//根據(jù)反射機(jī)制,查找subscriber中所有已onEvent開頭的method
Class<?> clazz=subscriber.getClass();
Method[] methods=clazz.getMethods();
for(Method method:methods){
String name=method.getName();
if(name.startsWith("onEvent")){
Class<?> param=method.getParameterTypes()[0];
ArrayList<Subscription> subscriptions=subscriptionsByEventType.get(param);
if(subscriptions==null){
subscriptions=new ArrayList<>();
subscriptionsByEventType.put(param,subscriptions);
}
//根據(jù)函數(shù)名字決定線程
if(name.substring("onEvent".length()).length()==0){
//onEvent 默認(rèn)為postThread
subscriptions.add(new Subscription(method,subscriber, ThreadMode.PostThread));
}else{
//onEventMainThread
subscriptions.add(new Subscription(method,subscriber, ThreadMode.MainThread));
}
}
}
}
首先利用反射獲取訂閱者所在類中所有以"onEvent"開頭的方法,獲取方法的參數(shù),根據(jù)參數(shù)類型去subscriptionsByEventType查找對應(yīng)的ArrayList<Subscription>,如果找不到,說明當(dāng)前還沒有訂閱者訂閱該類型的事件,就新建一個ArrayList<Subscription>,并將該參數(shù)類型與新建的ArrayList插入到HashMap中。
接下來就是構(gòu)建Subscription,這里默認(rèn)onEvent()為發(fā)生在PostThread,而onEventMainThread()發(fā)生在主線程main中。這樣,register方法就結(jié)束,是不是很簡單呢?
unregister:
public void unregister(Object subscriber){
Class<?> clazz=subscriber.getClass();
Method[] methods=clazz.getMethods();
for(Method method:methods){
String name=method.getName();
if(name.startsWith("onEvent")){
Class<?> param=method.getParameterTypes()[0];
ArrayList<Subscription> subscriptions=subscriptionsByEventType.get(param);
if(subscriptions!=null){
for(Subscription subscription:subscriptions){
if(subscription.subscriber==subscriber) subscriptions.remove(subscription);
}
}
}
}
}
懂了register怎么寫的寫unregister就簡單多了,同樣利用反射找到onEvent*方法,獲取參數(shù)類型,然后將該訂閱者的所有訂閱事件Sunscription從HashMap中去除即可。
最后是post方法:
public void post(Object eventType){
Class<?> clazz=eventType.getClass();
ArrayList<Subscription> subscriptions=subscriptionsByEventType.get(clazz);
if(subscriptions==null) Log.d(TAG,"EventBus: no subscriber has subscribed to this event");
else{
for(Subscription subscription:subscriptions){
switch (subscription.mode){
case MainThread:
mainThreadHandler.post(subscription,eventType);
break;
case PostThread:
try {
subscription.method.invoke(subscription.subscriber,eventType);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}
}
post中要做的事情是根據(jù)參數(shù)類型,從HashMap中取出所有訂閱該事件的Subscription,然后依次執(zhí)行每一個訂閱。根據(jù)訂閱事件發(fā)生的線程,這里分為兩種情況。PostThread很簡單,直接在post函數(shù)里執(zhí)行即可,具體方法為利用Method.Invoke(Object obj,Object...args);
subscription.method.invoke(subscription.subscriber,eventType);
接下來是要執(zhí)行應(yīng)該發(fā)生在MainThread中的訂閱事件,提到主線程,我們就聯(lián)想到了Handler,我們可以創(chuàng)建一個與主線程關(guān)聯(lián)的Handler,然后將事件的處理交給Handler。
MainThreadHandler:
public class MainThreadHandler extends Handler {
private Subscription subscription;
private Object eventType;
public MainThreadHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
subscription.method.invoke(subscription.subscriber,eventType);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public void post(Subscription subscription,Object eventType){
this.subscription=subscription;
this.eventType=eventType;
sendMessage(Message.obtain());
}
}
然后在建立EventBus的時候建立MainThreadHandler:
private EventBus(){
mainThreadHandler=new MainThreadHandler(Looper.getMainLooper());
};
至此,我們的EventBus全部完成,是不是很簡單呢?接下來就是測試。
我在MainActivity的界面中放了兩個 Button,分別對應(yīng)發(fā)生在MainThread以及子線程中。
寫了兩個訂閱函數(shù),訂閱事件類型都是ToastEvent:
public class ToastEvent {
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
Button bt;
Button btAsync;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
bt= (Button) findViewById(R.id.bt);
btAsync= (Button) findViewById(R.id.btAsync);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
EventBus.getDefault().post(new ToastEvent());
}
});
btAsync.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(new ToastEvent());
}
}).start();
}
});
}
public void onEvent(ToastEvent event){
Log.d("YJW","onEvent: "+ Thread.currentThread().getName());
}
public void onEventMainThread(ToastEvent event){
Toast.makeText(this, "Haha on MainThread", Toast.LENGTH_SHORT).show();
Log.d("YJW","onEventMainThread: "+ Thread.currentThread().getName());
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
點擊兩個Button都出現(xiàn)Toast,并打印相應(yīng)日志,上兩條日志為上面的Button,下面兩條日志對應(yīng)下面的Button,測試功能正常。
至此,大功告成。特別說明:真正的EventBus原理大致如此,不過更復(fù)雜,做了很多優(yōu)化與特殊處理,還利用了注解。但作為演示,此demo足夠了。