理論千萬篇山孔,不如實戰(zhàn)來一篇狞贱。
源碼 https://github.com/harvie1208/EventBus
關(guān)鍵詞:觀察者模式澈缺、反射、自定義注解塌鸯、線程調(diào)度
手寫200行代碼侍瑟,一步一步實現(xiàn)EventBus核心功能,看完可以寫一套屬于自己的事件總線庫啦!
不知大家平常在看博客的時候有沒有和我遇到一樣的問題丙猬,就是看的是懂非懂涨颜,好像懂了,又好像沒懂茧球。
主要有以下兩點:
? ? 1.文章缺少部分實現(xiàn)思路庭瑰,導(dǎo)致自己實現(xiàn)時卡住。
? ? 2.術(shù)語太過專業(yè)化抢埋,不易理解弹灭。
在求知的路上,我也看了不少文章揪垄,有非常優(yōu)秀的穷吮,也有缺這少那的。一路走來填了不少坑饥努,后面我會將所學知識點整理出來分享給大家捡鱼,盡量做到通俗易懂的理論加完整案例源碼。一方面是對自己知識點的總結(jié)回顧肪凛,另一方面也希望能幫助到有需要的同學少走彎路。因技術(shù)水平有限辽社,如有不正之處伟墙,還望各位不吝指教。
EventBus簡介
EventBus顧名思義就是事件總線,實際上就是一個`事件發(fā)布者/事件監(jiān)聽者(訂閱者)` 的框架, 發(fā)布者發(fā)布事件滴铅,Bus自動處理與分發(fā),監(jiān)聽者被動的接受戳葵。簡化各種異步和跳轉(zhuǎn)的通信。
![](https://user-gold-cdn.xitu.io/2019/7/18/16c059b4885766f6?w=1000&h=374&f=png&s=111792)
使用場景示例
? ? 1.短信驗證碼登陸場景
? ? ? ? 主登陸界面A->輸入手機號界面B->短信驗證碼界面C->登陸成功跳轉(zhuǎn)首頁D
? ? ? ? 需求:登陸成功后需要關(guān)閉A汉匙、B拱烁、C三個頁面
? ? 2.音樂播放場景
? ? ? ? 假如首頁有5個tab(包含5個fragment),每個fragment中都有音樂播放狀態(tài)小圖標
? ? ? ? 需求:音樂播放或暫停時生蚁,需要更新所有播放狀態(tài)圖標
核心思路
? ? 使用觀察者模式,在需要接收事件的方法上添加訂閱注解標識戏自,并將此方法所在對象添加到訂閱者集合,發(fā)送事件時遍歷訂閱者集合邦投,在通過反射調(diào)用相關(guān)訂閱方法。
代碼實戰(zhàn)
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標識
? ? 創(chuàng)建自定義注解@Subscribe用來標示訂閱方法
? ? 注解Annontation是Java5開始引入的新特征志衣,通俗來說就是為程序的元素(類、方法猛们、成員變量)添加標記用的
? ? @Target(ElementType.METHOD) //表示此注解作用域在方法上
? ? @Retention(RetentionPolicy.RUNTIME) //編譯程序處理完注解信息后存儲在class中念脯,可由VM讀入
? ? public @interface Subscribe {
? ? ? ? ThreadModel thread();//用于指定被注解方法執(zhí)行時所在的線程
? ? }
? ? 使用注解
? ? public class MainActivity extends AppCompatActivity {
? ? ? ? @Subscribe(thread = ThreadModel.BACKGROUND)//指定在子線程中執(zhí)行
? ? ? ? public void haha(LoginEvent loginEvent){
? ? ? ? ? ? Log.e("EventBus",loginEvent.getLoginStatus()+Thread.currentThread().getName());
? ? ? ? }
? ? }
3.注冊訂閱關(guān)系
? ? * 先聲明一個集合用于存儲類對象和被注解的方法及線程模式
? ? * 遍歷注冊對象的所有方法,取出帶有@Subscribe注解的方法
? ? * 獲取參數(shù)類型數(shù)組弯淘,當前僅支持一個參數(shù)
? ? * 獲取指定線程模式
? ? * 構(gòu)建訂閱者實例(方法绿店、參數(shù)類型、線程模式)庐橙,加入訂閱集合
? ? public class EventBus {
? ? ? ? //存儲訂閱類及方法參數(shù)
? ? ? ? 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;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //獲取參數(shù)類型集合
? ? ? ? ? ? ? ? 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.發(fā)送事件
? ? * 根據(jù)發(fā)送事件參數(shù)類型假勿,遍歷集合找到對應(yīng)方法
? ? * 判斷線程模式,主線程用handler處理怕午,子線程用線程池處理
? ? * 反射調(diào)用方法將事件傳過去
? ? public class EventBus {
? ? ? ? //存儲訂閱類及方法參數(shù)
? ? ? ? private Map<Object,List<Subscriber>> subscribeMethod;
? ? ? ? //線程調(diào)度
? ? ? ? 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調(diào)度到主線程
? ? ? ? ? ? ? ? ? ? mHandler.post(new EventRunable(busMethod, obj, eventParam));
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? //交由線程池處理
? ? ? ? ? ? ? ? ? ? executorService.execute(new EventRunable(busMethod, obj, eventParam));
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? 事件參數(shù)與接收參數(shù)類型一致即可废登,方法名隨意
? ? EventBus.getInstance().postEvent(new LoginEvent("登錄成功"));
## 總結(jié)
很多看似高大上的框架其實也沒我們想的那么難,寫著寫著就順手了郁惜,知而不行為不知堡距,快動起手來吧!
源碼 https://github.com/harvie1208/EventBus