理論千萬篇,不如實戰來一篇。
關鍵詞:觀察者模式、反射、自定義註解、執行緒排程
手寫200行程式碼,一步一步實現EventBus核心功能,看完可以寫一套屬於自己的事件匯流排庫啦!
不知大家平常在看部落格的時候有沒有和我遇到一樣的問題,就是看的是懂非懂,好像懂了,又好像沒懂。
主要有以下兩點:
- 1.文章缺少部分實現思路,導致自己實現時卡住。
- 2.術語太過專業化,不易理解。
在求知的路上,我也看了不少文章,有非常優秀的,也有缺這少那的。一路走來填了不少坑,後面我會將所學知識點整理出來分享給大家,儘量做到通俗易懂的理論加完整案例原始碼。一方面是對自己知識點的總結回顧,另一方面也希望能幫助到有需要的同學少走彎路。因技術水平有限,如有不正之處,還望各位不吝指教。
EventBus簡介
EventBus顧名思義就是事件匯流排,實際上就是一個事件釋出者/事件監聽者(訂閱者)
的框架, 釋出者釋出事件,Bus自動處理與分發,監聽者被動的接受。簡化各種非同步和跳轉的通訊。
使用場景示例
1.簡訊驗證碼登陸場景
主登陸介面A->輸入手機號介面B->簡訊驗證碼介面C->登陸成功跳轉首頁D
需求:登陸成功後需要關閉A、B、C三個頁面
複製程式碼
2.音樂播放場景
假如首頁有5個tab(包含5個fragment),每個fragment中都有音樂播放狀態小圖示
需求:音樂播放或暫停時,需要更新所有播放狀態圖示
複製程式碼
核心思路
使用觀察者模式,在需要接收事件的方法上新增訂閱註解標識,並將此方法所在物件新增到訂閱者集合,
傳送事件時遍歷訂閱者集合,在通過反射呼叫相關訂閱方法。
複製程式碼
程式碼實戰
1.編寫EventBus核心類,使用單例模式提供唯一例項
public class EventBus {
private static EventBus myBus;
public static EventBus getInstance(){
if (myBus==null){
synchronized (EventBus.class){
if (myBus==null){
myBus = new EventBus();
}
}
}
return myBus;
}
}
複製程式碼
2.給訂閱方法新增@Subscribe標識
-
建立自定義註解@Subscribe用來標示訂閱方法
註解Annontation是Java5開始引入的新特徵,通俗來說就是為程式的元素(類、方法、成員變數)新增標記用的
@Target(ElementType.METHOD) //表示此註解作用域在方法上 @Retention(RetentionPolicy.RUNTIME) //編譯程式處理完註解資訊後儲存在class中,可由VM讀入 public @interface Subscribe { ThreadModel thread();//用於指定被註解方法執行時所在的執行緒 } 複製程式碼
-
使用註解
public class MainActivity extends AppCompatActivity { @Subscribe(thread = ThreadModel.BACKGROUND)//指定在子執行緒中執行 public void haha(LoginEvent loginEvent){ Log.e("EventBus",loginEvent.getLoginStatus()+Thread.currentThread().getName()); } } 複製程式碼
3.註冊訂閱關係
-
先宣告一個集合用於儲存類物件和被註解的方法及執行緒模式
-
遍歷註冊物件的所有方法,取出帶有@Subscribe註解的方法
-
獲取引數型別陣列,當前僅支援一個引數
-
獲取指定執行緒模式
-
構建訂閱者例項(方法、引數型別、執行緒模式),加入訂閱集合
public class EventBus { //儲存訂閱類及方法引數 private Map<Object,List<Subscriber>> subscribeMethod; public void register(Object obj){ if (obj==null){ return; } Class<?> mclazz = obj.getClass(); //獲取本類所有方法 Method[] methods = mclazz.getDeclaredMethods(); List<Subscriber> methods1 = new ArrayList<>(); for (Method method : methods){ //獲取帶有我們Subscribe註解的方法 Subscribe subscribe = method.getAnnotation(Subscribe.class); if (subscribe==null){ continue; } //獲取引數型別集合 Class<?>[] typeVariable = method.getParameterTypes(); if (typeVariable.length!=1){ continue; } ThreadModel threadModel = subscribe.thread(); Subscriber busMethod = new Subscriber(method,threadModel,typeVariable[0]); methods1.add(busMethod); } if (methods1.size()>0){ subscribeMethod.put(obj,methods1); } } } 複製程式碼
4.登出訂閱
- 將此訂閱物件移除訂閱集合
public void unRegister(Object object){ if (subscribeMethod.containsKey(object)){ subscribeMethod.remove(object); } } 複製程式碼
5.傳送事件
-
根據傳送事件引數型別,遍歷集合找到對應方法
-
判斷執行緒模式,主執行緒用handler處理,子執行緒用執行緒池處理
-
反射呼叫方法將事件傳過去
public class EventBus { //儲存訂閱類及方法引數 private Map<Object,List<Subscriber>> subscribeMethod; //執行緒排程 private Handler mHandler; //執行緒池 private ExecutorService executorService; private EventBus(){ subscribeMethod = new HashMap<>(); mHandler = new Handler(Looper.getMainLooper()); executorService = Executors.newCachedThreadPool(); } public void postEvent(Object eventParam){ Set<Object> set = subscribeMethod.keySet(); Iterator<Object> iterable =set.iterator(); while (iterable.hasNext()){ Object obj = iterable.next(); List<Subscriber> busMethodList = subscribeMethod.get(obj); for (Subscriber busMethod : busMethodList){ if(busMethod.getParamsType() == eventParam.getClass()){ invoke(obj,busMethod,eventParam); } } } } private void invoke(final Object obj, final Subscriber busMethod, final Object eventParam){ switch (busMethod.getThreadModel()){ case MAIN: //通過handler排程到主執行緒 mHandler.post(new EventRunable(busMethod, obj, eventParam)); break; default: //交由執行緒池處理 executorService.execute(new EventRunable(busMethod, obj, eventParam)); break; } } } 複製程式碼
事件引數與接收引數型別一致即可,方法名隨意
EventBus.getInstance().postEvent(new LoginEvent("登入成功")); 複製程式碼
總結
很多看似高大上的框架其實也沒我們想的那麼難,寫著寫著就順手了,知而不行為不知,快動起手來吧!