手寫簡化EventBus,理解框架核心原理

前言

本來想學習下EventBus最新框架的源碼,但是最新的框架的代碼量已經很大了,很多都是錦上添花的東西,很多核心的原理代碼需要從中剝離出來去了解。但是對於剛開始看源碼就直接拿到這麼功能豐富並完善的代碼,可能收效甚微。爲了自己學習並且幫助讀者同志們學習,這裏自己根據以前學習的經驗理解,手寫一份簡化Eventbus源碼,和大家一起學習此框架的要義。

EventBus技術架構

簡介

Eventbus的存在是爲了解決組件間的簡單通信問題,採用發佈-訂閱者模式,通過統一的管理機構(bus總線)維護所有的訂閱者,將發佈者發佈的消息發送對應訂閱者的處理方法處理。
發佈者和訂閱者可以是任意Activity、Fragment、Service、Thread等,只要在一個進程中就可以,其不支持多進程。目前git地址爲:https://github.com/greenrobot/EventBus。截至今日,最新版本3.2.0。
使用方法:

EventBus in 3 steps

   // 1.Define events: 定義Event,這裏爲用戶使用時自定義的部分,框架不包含此格式,爲任意java bean包含自己需要傳輸的信息。

    public static class MessageEvent { /* Additional fields if needed */ }


    //2 Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
    //準備訂閱者方法,方法名是任意的,只是必須帶@Subscribe註解,必須只有一個參數,參數需是第一步發送的消息。

    @Subscribe(threadMode = ThreadMode.MAIN)  
    public void onMessageEvent(MessageEvent event) {/* Do something */};

    //3.Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:
   // 註冊和解註冊eventbus,可以在activity和fragment、或者線程中,根據場景需要的時機進行註冊解註冊。

     @Override
     public void onStart() {
         super.onStart();
         EventBus.getDefault().register(this);
     }

     @Override
     public void onStop() {
         super.onStop();
         EventBus.getDefault().unregister(this);
     }

    //4.Post events: 發送event,可以是任何地方

     EventBus.getDefault().post(new MessageEvent());

架構原理

此架構圖爲EventBus原版的架構圖。
在這裏插入圖片描述中間的EventBus是個大管家,負責所有消息流轉和註冊信息保存,程序運行起來時,註冊者如Activity調用了register方法並把自己的this作爲參數傳入,大管家會登記所有的登記者和接收處理方法(@Subscribe註解方法)。當有發佈者發佈消息,即調用了EventBus.getDefault().post方法,將參數自定義Event發佈到管家EventBus中, 大管家找到這個進程大家庭中所有註冊了此Event類型的方法,並進行調用處理。

手寫框架

模塊

要手寫框架,即需要包括框架核心原理和實現核心方法。這裏進行列舉,以明確程序框架。根據上節EventBus簡介的使用方法可知需要實現:

  • 自定義註解@Subscribe和線程類型枚舉。
  • EventBus類,並實現註冊register方法、解註冊方法unregister、post方法,且有getDefault單例方法。
  • 獲取所有註冊類中的加了@Subscribe註解的方法、方法參數和註解信息(線程和優先級信息)。
  • 實現post邏輯,即將發送Event到對應訂閱方法參數是Event類型的方法

源碼設計

菜譜有了,那就可以開始做菜了。如果想同時看着源碼看文章的話,可以從github查看Demo。https://github.com/qingdaofu1/ZephyrBus
自定義註解和創建ThreadMode枚舉類
自定義註解可以添加默認值,並且這裏標註此註解是使用在方法上,並且是運行時保留策略,關於註解的詳細原理可以參照我的另一篇總結:Android註解-看這篇文章就夠了

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    int priority() default 0;
}

線程模型肯定包括默認值POSTING,即和發送線程保持一致。MAIN線程即主線程,BACKGROUND即後臺線程,非主線程。這裏僅作示例,可以學習EventBus增加其他選項進行串行隊列的處理或者線程池多線程等特性。

public enum ThreadMode {
    POSTING,
    MAIN,
    BACKGROUD
}

創建MyEventBus類和必要方法

  • 數據結構

爲了保存訂閱者的處理方法需要兩個數據結構。
由於用戶不一定在一個地方註冊,即如果進程中Activity中、Fragment、Service等都有註冊,則需要一個結合記錄每個註冊類中所有的訂閱方法,這個集合即typesBySubscriber 。
然後比如用戶有多種Event類型,比如Aevent、Bevent…,需要有個能記錄所有處理Aevent的訂閱者方法類和所有處理Bevent的方法類。如果有更多則記錄更多。這個數據結構即subscriptionsByEventType ;其中的CopyOnWriteArrayList數據結構用法可以參考:CopyOnWriteArrayList的原理及使用
這裏學習了源碼中的數據結構名,以便讀者返回讀源碼時更容易理解。

/**
     * 同一類型EventType類與所有註冊方法的集合
     */
    private Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType = new HashMap<>();
    /**
     * 所有類有註冊MyEventBus的  類與其內部所有處理方法集合。
     */
    private static final Map<Object, List<Class<?>>> typesBySubscriber = new HashMap<>();
    
    //訂閱處理方法收集和調用策略抽象類,目前只實現了反射策略,另增加了APT類,待後邊完善
	MethodInvokeStrategy invokeStrategy;

MethodInvokeStrategy爲抽象方法類,定義了收集訂閱方法和調用方法的接口,並創建了主線程的Handler和工作線程Handler供調用時使用。

public abstract class MethodInvokeStrategy {
    private static final String TAG = "MethodInvokeStrategy";

    private static HandlerThread handlerThread = new HandlerThread("workThread");

    protected static Handler mainHandler;

    protected static Handler workHander;

    public MethodInvokeStrategy() {
        handlerThread.start();
        mainHandler = new Handler(Looper.getMainLooper());
        workHander = new Handler(handlerThread.getLooper());
    }

    public List<SubscribedMethod> getAllSubscribedMethods(Object subscriber) {
        return null;
    }

    public void invokeMethod(Subscription subscription, Object event) {

    }

這裏的必要方法包括單例方法getDefault、註冊方法register、解註冊方法unregister、發送方法post。

  • getDefault方法單例

這是較常用寫法,如果想寫不一樣的單例可以參考:
設計模式之單例模式的幾種寫法

/**
 * 單例
 *  * @return
 */
public static MyEventBus getDefault() {
    if (instance == null) {
        synchronized (MyEventBus.class) {
            if (instance == null) {
                instance = new MyEventBus();
            }
        }
    }
    return instance;
}
  • register方法
    根據上邊描述的,爲了能把發佈者的Event消息發佈到所有的訂閱者,需要記錄所有處理Event的消息方法類信息。這裏包含實際的處理方法,最終返回上一步的存儲集合。
    由於register將當前類的this傳入,即相當於獲取了這個類的實例,即能夠拿到其內的所有方法參數等信息。這些都是供實際收集方法的類需要的。
 /**
     * 註冊subscriber到MyEventBus,並獲取其所有加了{@link Subscribe} 的方法,並放入集合中
     *
     * @param subscriber 訂閱者類,即通過register將this參數傳過來的類,可以是activity、service、fragment、thread等。
     */
    public void register(Object subscriber) {
        List<SubscribedMethod> allSubscribedMethods = invokeStrategy.getAllSubscribedMethods(subscriber);
        for (SubscribedMethod subscribedMethod : allSubscribedMethods) {
            Class<?> eventType = subscribedMethod.getEventType();
            CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
            if (subscriptions == null) {
                subscriptions = new CopyOnWriteArrayList<>();
                subscriptionsByEventType.put(eventType, subscriptions);
            }
            // TODO: 2020/4/18  如果做priority邏輯,則在此處需要排序添加
            subscriptions.add(new Subscription(subscriber, subscribedMethod));
            // 獲取這個訂閱者類中記錄的所有的eventType類型
            List<Class<?>> eventTypesInSubscriber = typesBySubscriber.get(subscriber);
            if (eventTypesInSubscriber == null) {
                eventTypesInSubscriber = new ArrayList<>();
                typesBySubscriber.put(subscriber, eventTypesInSubscriber);
            }
            eventTypesInSubscriber.add(eventType);
        }
        printTypesBySubscriber(typesBySubscriber, subscriber);
    }
  • unregister方法

解註冊就比較簡單了,直接將集合中對應的註冊類的key去除。

/**
 * 解註冊eventbus
 *
 * @param subscriber
 */
public void unregister(Object subscriber) {
    if (typesBySubscriber != null) {
        typesBySubscriber.remove(subscriber);
    }
}

post方法
post方法將參數event發送,發送時根據eventbus中大管家保存的所有能處理此event的方法發送過去處理。

/**
     * 發送event消息到訂閱者 處理方法
     *
     * @param event
     */
    public void post(Object event) {
        if (subscriptionsByEventType.size() <= 0) {
            Log.e(TAG, "post: no any eventbus registed named" + event.toString());
            return;
        }
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(event.getClass());
        for (Subscription subscription : subscriptions) {
            invokeStrategy.invokeMethod(subscription, event);
        }
    }

到這裏基本所有的方法都定義好了,理解上我們可以把整個EventBus的架構原理能夠理解透了。但是實際的反射策略還沒有講。
實現獲取所有訂閱者處理方法,這裏既然是反射實現的,自然需要懂反射的原理,這裏我在寫的時候也是很多的不熟悉,後邊會跟上一篇介紹反射的文章,聽別人講或者看別人文章不如自己去實現下,你會發現寫的時候好像不是那麼回事,還是有些不理解的。
總之核心思想就是,通過註冊類,可以獲取所有的方法,然後獲取方法的註解,獲取註解的值,獲取帶特定註解方法的Event類,這些都是大管家需要收集的資料。

/**
     * 獲取這個訂閱者類中所有的帶{@link Subscribe}的方法
     *
     * @param subscriber 訂閱者類,即通過register將this參數傳過來的類,可以是activity、service、fragment、thread等。
     */
    @Override
    public List<SubscribedMethod> getAllSubscribedMethods(Object subscriber) {
        //記錄訂閱者方法參數
        //CopyOnWriteArrayList<Subscription> subscribedMethods = new CopyOnWriteArrayList<Subscription>();
        List<SubscribedMethod> subscribedMethods = new ArrayList<>();
        Class<?> aClass = subscriber.getClass();
        //獲取所有方法
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.isAnnotationPresent(Subscribe.class)) {
                Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                if (parameterTypes == null || parameterTypes.length > 1) {
                    throw new IllegalArgumentException("參數不能爲空,且只能有一個參數");
                }
                Class<?> parameterType = parameterTypes[0];
                Log.d(TAG, "getAllSubscribedMethods: parameterType=" + parameterType.getName());
                Subscribe annotation = declaredMethod.getAnnotation(Subscribe.class);
                int priority = annotation.priority();
                ThreadMode threadMode = annotation.threadMode();
                SubscribedMethod subscribedMethod = new SubscribedMethod(declaredMethod, parameterType, threadMode, priority);
                Log.d(TAG, "getAllSubscribedMethods: subscribedMethod=" + subscribedMethod.toString());
                subscribedMethods.add(subscribedMethod);
            }
        }
        return subscribedMethods;
    }

最後便是根據線程模型調用方法的實現了,這裏首先想想上邊說的抽象類的兩個handler,有主線程的和工作線程的handler。這裏可以根據註解的值ThreadMode進行對應處理。相信大家對Handler也比較認識,如果需要進一步理解Handler和線程的關係,可以查看:Handler通信機制源碼解讀

public void invokeMethod(Subscription subscription, final Object event) {
        final Object subscriber = subscription.getSubscriber();
        SubscribedMethod subscribedMethod = subscription.getSubscribedMethod();
        final Method method = subscribedMethod.getMethod();
        switch (subscribedMethod.getThreadMode()){
            case POSTING:
                Log.d(TAG, "invokeMethod: ThreadMode=POSTING");
                invoke(method, subscriber, event);
                break;
            case MAIN:
                Log.d(TAG, "invokeMethod: ThreadMode=MAIN");
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        invoke(method, subscriber, event);
                    }
                });
                break;
            case BACKGROUD:
                Log.d(TAG, "invokeMethod: ThreadMode=BACKGROUND");
                workHander.post(new Runnable() {
                    @Override
                    public void run() {
                        invoke(method, subscriber, event);
                    }
                });
                break;
        }

    }

    private void invoke(Method method, Object subscriber, Object event){
        try {
            method.invoke(subscriber, event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

源碼測試

首先在測試module中添加發送Event源碼,btn_send1爲主線程發送,btn_send2會從主線程和線程中交替發送。而處理端則在註解Subscribe中分別使用默認線程和主線程。

findViewById(R.id.btn_send1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyEventBus.getDefault().post(new WorkEvent(5));
            }
        });
        findViewById(R.id.btn_send2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isThreadMain) {
                    MyEventBus.getDefault().post(new ViewEvent("主線程測試文字"));
                } else {
                    new Thread() {
                        @Override
                        public void run() {
                            super.run();
                            MyEventBus.getDefault().post(new ViewEvent("子線程測試文字"));
                        }
                    }.start();
                }
                isThreadMain = !isThreadMain;
            }
        });
        
 @Subscribe()
    public void onEvent(final WorkEvent event) {
     //...省略
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void handleView(final ViewEvent event) {
      //...省略
        });
    }

測試結果:
在這裏插入圖片描述本Demo的github地址爲:https://github.com/qingdaofu1/ZephyrBus,希望有需要的同學可以下載下來實操下加深理解。
下一步計劃是要增加apt模式的策略,有需要的可以繼續關注。

紙上得來終覺淺,絕知此事要躬行。
喜歡的可以點個贊鼓勵下,有問題可以留言溝通。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章