200行代碼手寫一套EventBus事件總線


理論千萬篇,不如實戰(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)秀的,也有缺這少那的。一路走來填了不少坑,后面我會將所學(xué)知識點整理出來分享給大家,盡量做到通俗易懂的理論加完整案例源碼。一方面是對自己知識點的總結(jié)回顧,另一方面也希望能幫助到有需要的同學(xué)少走彎路。因技術(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->輸入手機(jī)號界面B->短信驗證碼界面C->登陸成功跳轉(zhuǎn)首頁D

? ? ? ? 需求:登陸成功后需要關(guān)閉A、B、C三個頁面

? ? 2.音樂播放場景

? ? ? ? 假如首頁有5個tab(包含5個fragment),每個fragment中都有音樂播放狀態(tài)小圖標(biāo)

? ? ? ? 需求:音樂播放或暫停時,需要更新所有播放狀態(tài)圖標(biāo)

核心思路

? ? 使用觀察者模式,在需要接收事件的方法上添加訂閱注解標(biāo)識,并將此方法所在對象添加到訂閱者集合,發(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標(biāo)識

? ? 創(chuàng)建自定義注解@Subscribe用來標(biāo)示訂閱方法

? ? 注解Annontation是Java5開始引入的新特征,通俗來說就是為程序的元素(類、方法、成員變量)添加標(biāo)記用的

? ? @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ù)組,當(dāng)前僅支持一個參數(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容