事件總線模式——實例講解
上一篇博客我們講解了事件總線模式的概念及其實現原理 ,我們在實際運用中,大多數是在Android開發中運用到事件總線模式,它實現了事件訂閱者和事件發布者的解耦,讓我們更加容易在actvity等組件間傳遞信息。這篇文章通過這個簡單的實例,給大家說明EventBus實現的原理,一起來打造一個簡單的事件總線框架。如果你明白了這個框架的設計原理,那麽EventBus也就相差不大,兩者比起來只是後者更加完善和高效。
由於是模仿EventBus寫得,所以我也把這個“框架”稱為EventBus,先來看一下我們怎麽使用這個框架。
現在我們實現的一個效果是,有兩個acivity如圖MainActivity:
這其實就是一個消息傳遞的過程,假設我們用EventBus,下面我們來看一下,代碼是怎麽寫的。
public class MainActivity extends FragmentActivity { Button btn; TextView text; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EventBus.getInstance().register(this); btn = (Button) findViewById(R.id.btn); text = (TextView) findViewById(R.id.text); btn.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View v) { startActivity(new Intent(MainActivity.this,SecondActivity.class)); } }); } public void onEvent(Info i){ Log.i("cky", i.msg); } public void onEventMain(Info i){ text.setText(i.msg); } public void onEventMain(Info2 i){ text.setText(text.getText()+i.msg); } }
可以看到,我們在onCreate()裏面調用了EventBus.register(this),來註冊事件然後又兩者函數,一個是onEvent(),這個方法裏面的代碼,會在一個子線程中執行一個是onEventMain(),這個方法裏面的代碼,會在UI線程執行。在這裏,我們改變了TextView中的文字。使用過EventBus的朋友,現在看來使用方式是不是跟EventBus很像(我就是想實現EventBus的效果啊)。EventBus對事件在不同線程中處理,有四種方式,但是我們這裏只寫了簡單的兩者,主線程和非主線程,這樣代碼更加簡單,而且原理是一樣的,會寫兩個,就會寫多個。然後SecondActivity裏面,大家一定知道是怎麽回事,肯定是在點擊裏面調用了post()方法。
public class SecondActivity extends Activity { Button btn2; Button btn3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); btn2 = (Button) findViewById(R.id.btn2); btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EventBus.getInstance().post(new Info("信息1")); } }); btn3 = (Button) findViewById(R.id.btn3); btn3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EventBus.getInstance().post(new Info2("信息2")); } }); } }
就是這麽簡單,使用方法跟EventBus如出一轍。
大家可能會好奇到底怎麽樣用簡單的代碼,實現這樣的效果,下面我帶大家看我的代碼。!
先看EventBus
public class EventBus { HashMap<Class<?>,ArrayList<Subscription>> subscriptionsByEventType = new HashMap<Class<?>,ArrayList<Subscription>>(); MainThreadHandler mainThreadHandler = new MainThreadHandler(this,Looper.getMainLooper()); AsyThreadHandler asyThreadHandler = new AsyThreadHandler(this); private final static EventBus instance = new EventBus(); public static EventBus getInstance(){ return instance; } private EventBus(){}; }
有幾個地方:
1,將構造函數設為私有,使用了getInstance()來給外界提供EventBus實例,其實就是一個單例模式
2,有幾個復雜的屬性,首先是subscriptionsByEventType,這是一個map,key代表某個bean類。
使用過EventBus都知道,我們在傳遞信息的時候,要傳遞一個實體類,例如我上面的例子,就是Info這個類。
value值是一個ArrayList<Subscription>,Subscription的意義就是一個訂閱,是一個我創建的實體類。這個類的具體含義留到後面講。
剩下兩個mainThreadHandler和asyThreadHandler,顧名思義就是分別用來處理主線程和子線程的,都是我創建的類。
看到這裏可能大家有點糊塗,出現了三個類,別急,我一定會解釋清楚。現在先讓我們來看非常重要的register()方法。
public void register(Object subscriber){ Class<?> clazz = subscriber.getClass();//獲取訂閱者的類型 Method[] methods = clazz.getMethods();//獲取該類的所有方法 for(Method m:methods){//遍歷方法 String name = m.getName();//獲取方法名 if(name.startsWith("onEvent")){//判斷方法名是否以"onEvent"開頭 </span> Class<?>[] params = m.getParameterTypes();//獲取該方法的參數類型 ArrayList<Subscription> arr; if(subscriptionsByEventType.containsKey(params[0])){ arr = subscriptionsByEventType.get(params[0]); }else{ arr = new ArrayList<Subscription>(); } int len = name.substring("onEvent".length()).length();//截取方法除"onEvent"部分 Subscription sub; if(len==0){//如果剩余長度為0,說明是子線程中執行 sub = new Subscription(subscriber,new SubscriberMethod(m,params[0],0)); }else{//否則,在主線程執行 sub = new Subscription(subscriber,new SubscriberMethod(m,params[0],1)); } arr.add(sub); subscriptionsByEventType.put(params[0],arr); } } }
從上面的註釋我們可以看到,我們先獲取了訂閱者(例子中是MainActivity)的方法,找到onEvent開頭的方法,獲得它們的參數類型
然後判斷subscriptionsByEventType是否有以這些參數類型為key的數據,如果沒有,新建一個ArrayList<Subscription>。
然後我們先看Subscription
public class Subscription { Object subscriber; SubscriberMethod SubscriberMethod; public Subscription(Object subscriber, SubscriberMethod SubscriberMethod) { this.subscriber = subscriber; this.SubscriberMethod = SubscriberMethod; } }
它代表一個訂閱,擁有subsriber,也就是訂閱者
還有一個SubscriberMethod,這是訂閱方法類
public class SubscriberMethod { Method m; int type; public SubscriberMethod(Method m, Class<?> param, int type) { this.m = m; this.type = type; } }
它有一個Method m屬性,就是我們註冊的方法,另外一個type就是標記,0代表m在子線程中執行,其余代表在主線程中執行。
所以總的來說register()通過遍歷訂閱者,找到訂閱方法(onEvent(),onEventMain()),將方法包裝成SubscriberMethod類,再將SubscriberMethod和訂閱者一起,
包裝成Subscription類。
而subscriptionsByEventType根據參數類型為key,所謂參數類型,在例子中就是Info.class和info2.class
為什麽我們要用參數類型為鍵呢?我們來想,以後我們在post的時候,是這樣調用post的:
EventBus.getINstance().post(new Info("信息"));
我們要傳進去一個參數,而EventBus的任務就是,調用所有註冊了這個參數類型的訂閱方法。
所有顯然,我們要根據參數類型,找到這些方法,這是key是參數類型的原因了。
Ok,register()方法下來,最重要的一件事就是我們得到了subscriptionsByEventType
接下來我們就可以看post方法了
public void post(Object event){ Class<?> clazz = event.getClass(); ArrayList<Subscription> arr = subscriptionsByEventType.get(clazz); for(Subscription sub:arr){//遍歷訂閱 if(sub.SubscriberMethod.type==0){ asyThreadHandler.post(sub,event); }else{ mainThreadHandler.post(sub,event); } } }
post()方法裏面,如同我們上面所說,獲取了參數類型,然後在subscriptionsByEventType中查詢所有改類型對應的訂閱Subscription
對於Subscription,它有我們訂閱類的所有信息。
首先根據type判斷是在主線程還是子線程執行,然後調用一開始講到的兩個類的實例就好了。
我們先看asyThreadHandler,這個會在子線程執行
public class AsyThreadHandler { private EventBus eventBus; AsyThreadHandler(EventBus eventBus) { this.eventBus = eventBus; } public void post(final Subscription sub, final Object event){ new Thread(){ @Override public void run() { eventBus.invoke(sub,event,sub.SubscriberMethod.m); } }.start(); } }
代碼很簡單,其實它的post方法,就是啟用了一個線程,然後在線程裏面,調用了EventBus的invoke()方法。
我們看這個方法
public void invoke(final Subscription sub, final Object event,Method m){ try { m.invoke(sub.subscriber,event); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
其實只有一句話,就是調用了反射去執行方法。m是訂閱方法,sub.subscriber就是訂閱者,event就是post()方法傳入的實體
這樣我們就在子線程中調用了這個方法了,相當於MainActivity主動調用這個方法。
那麽在主線程中呢,大同小異
public class MainThreadHandler extends Handler{ private final EventBus eventBus; MainThreadHandler(EventBus eventBus, Looper looper) { super(looper); this.eventBus = eventBus; } @Override public void handleMessage(Message msg) { EventBus.getInstance().invoke(sub,event,sub.SubscriberMethod.m); } Subscription sub; Object event; public void post(Subscription sub, Object event){ this.sub = sub; this.event = event; sendEmptyMessage(0); } }
可以看到繼承了handler,表明在主線程中調用。
post方法傳遞了一個空message,在handlerMessage()方法裏面,又是調用了EventBus的invoke()方法。
殊途同歸,最後都是調用EventBus的invoke()方法,不過一個在子線程中調用,一個在主線程中調用。
就這樣,我們輕而易舉得實現了EventBus的基本功能哦!
是不是覺得代碼很簡單,其實上面的代碼,已經將EventBus框架的思路將清楚了,大家明白這思路,再看EventBus就容易多了。
事件總線模式——實例講解