200代碼寫一套屬於本身的事件總線(EventBus)庫

理論千萬篇,不如實戰來一篇。

源碼 github.com/harvie1208/…git

關鍵詞:觀察者模式、反射、自定義註解、線程調度github

手寫200行代碼,一步一步實現EventBus核心功能,看完能夠寫一套屬於本身的事件總線庫啦!數組

不知你們日常在看博客的時候有沒有和我遇到同樣的問題,就是看的是懂非懂,好像懂了,又好像沒懂。bash

主要有如下兩點:框架

  • 1.文章缺乏部分實現思路,致使本身實現時卡住。
  • 2.術語太過專業化,不易理解。

在求知的路上,我也看了很多文章,有很是優秀的,也有缺這少那的。一路走來填了很多坑,後面我會將所學知識點整理出來分享給你們,儘可能作到通俗易懂的理論加完整案例源碼。一方面是對本身知識點的總結回顧,另外一方面也但願能幫助到有須要的同窗少走彎路。因技術水平有限,若有不正之處,還望各位不吝指教。異步

EventBus簡介

EventBus顧名思義就是事件總線,實際上就是一個事件發佈者/事件監聽者(訂閱者) 的框架, 發佈者發佈事件,Bus自動處理與分發,監聽者被動的接受。簡化各類異步和跳轉的通訊。oop

使用場景示例

1.短信驗證碼登錄場景post

主登錄界面A->輸入手機號界面B->短信驗證碼界面C->登錄成功跳轉首頁D
需求:登錄成功後須要關閉A、B、C三個頁面
複製代碼

2.音樂播放場景ui

假如首頁有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用來標示訂閱方法spa

    註解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("登陸成功"));
    複製代碼

總結

不少看似高大上的框架其實也沒咱們想的那麼難,寫着寫着就順手了,知而不行爲不知,快動起手來吧!

源碼 github.com/harvie1208/…

相關文章
相關標籤/搜索