200程式碼寫一套屬於自己的事件匯流排(EventBus)庫

harvie發表於2019-07-18

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

原始碼 github.com/harvie1208/…

關鍵詞:觀察者模式、反射、自定義註解、執行緒排程

手寫200行程式碼,一步一步實現EventBus核心功能,看完可以寫一套屬於自己的事件匯流排庫啦!

不知大家平常在看部落格的時候有沒有和我遇到一樣的問題,就是看的是懂非懂,好像懂了,又好像沒懂。

主要有以下兩點:

  • 1.文章缺少部分實現思路,導致自己實現時卡住。
  • 2.術語太過專業化,不易理解。

在求知的路上,我也看了不少文章,有非常優秀的,也有缺這少那的。一路走來填了不少坑,後面我會將所學知識點整理出來分享給大家,儘量做到通俗易懂的理論加完整案例原始碼。一方面是對自己知識點的總結回顧,另一方面也希望能幫助到有需要的同學少走彎路。因技術水平有限,如有不正之處,還望各位不吝指教。

EventBus簡介

EventBus顧名思義就是事件匯流排,實際上就是一個事件釋出者/事件監聽者(訂閱者) 的框架, 釋出者釋出事件,Bus自動處理與分發,監聽者被動的接受。簡化各種非同步和跳轉的通訊。

200程式碼寫一套屬於自己的事件匯流排(EventBus)庫

使用場景示例

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("登入成功"));
    複製程式碼

總結

很多看似高大上的框架其實也沒我們想的那麼難,寫著寫著就順手了,知而不行為不知,快動起手來吧!

原始碼 github.com/harvie1208/…

相關文章