移動架構 (四) EventBus 3.1.1 源碼分析及實現本身的輕量級 EventBus 框架,根據 TAG 發送接收事件。

EventBus 我相信你們不會很默認,應該也都在項目中使用過,雖然 EventBus 在項目中不便於管理,發射出去的消息不便於跟蹤或者閱讀,可是不能否認它是一個優秀的開源項目,仍是值得咱們你們學習的,我我的感受惟一不足的就是 EventBus 功能上不能根據 TAG 來進行發射和接收消息,只能經過註解 + 消息類型進行找消息。那麼咱們本身能夠實現這個功能嗎?不能否認,固然能夠! 想要實現這個功能,咱們先大概簡要的瞭解下 EventBus 使用及源碼是怎麼實現的。java

EventBus 簡單使用

EventBus 能夠代替 Android 傳統的 Intent, Handler, Broadcast 或接口函數, 在 Fragment, Activity, Service 線程間進行數據傳遞。git

添加 EventBus 到項目中

implementation 'org.greenrobot:eventbus:3.1.1'
複製代碼

register

EventBus.getDefault().register(this);
複製代碼

註解實現接收的 Event

@Subscribe(threadMode = ThreadMode.MAIN)
    public void receive(String event){
        Log.d(TAG,"接收到 EventBus post message:" + event);
    }
複製代碼

發射數據

EventBus.getDefault().post("發射一個測試消息");
複製代碼

註銷註冊的事件

EventBus.getDefault().unregister(this);
複製代碼

這裏就簡單介紹下 EventBus 使用,想詳細瞭解的能夠看 EventBus GitHubgithub

EventBus 3.1.1 源碼分析

上面小節我們學習了 EventBus 的簡單使用,那麼咱們就根據上面使用到的來進行源碼分析。設計模式

register

流程圖:緩存

EventBus-register.png

register 代碼:bash

/** * *EventBus register */
    public void register(Object subscriber) {
      //1. 拿到當前註冊 class
        Class<?> subscriberClass = subscriber.getClass();
      //2. 查找當前 class 類中全部訂閱者的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
              //3. 
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
複製代碼

subscribe(x,x)代碼:架構

//必須加鎖調用
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
      //拿到訂閱者參數類型
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
      //根據參數類型,拿到當前全部訂閱者
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
      //4. 若是沒有拿到,則存進去緩存中
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
          //若是已經存在相同類型的註冊事件,就拋出異常
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

       ......
    }
複製代碼
  1. 拿到當前傳入進來的 this 對象。框架

  2. 查找當前 this 類中全部訂閱的函數。異步

  3. subscriptionsByEventType 完成數據初始化。async

    subscriptionsByEventType = new HashMap();
    複製代碼

    // 參考上圖註釋 4 ,根據參數類型存儲訂閱者和訂閱方法

post

流程圖:

EventBus-post.png

代碼

/** 發送事件*/
    public void post(Object event) {
      //在當前線程中取出變量數據
        PostingThreadState postingState = currentPostingThreadState.get();
      //將須要發送的數據添加進當前線程的隊列中
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                  //開啓取出數據
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }
複製代碼
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {
          //查找全部的事件類型
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
          //發送事件
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
複製代碼
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
          //這裏的容器就是 register 的時候添加進行去的,如今拿出來
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                  //發送到訂閱者哪裏去
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }
複製代碼
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
      //根據訂閱者 threadMode 來進行發送
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
            //經過反射發送
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
            //若是是主線程直接在當前線程發送
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                  //開啓一個子線程發送
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }
複製代碼

步驟

  1. post
  2. 獲取事件類型
  3. 根據類型,獲取訂閱者和訂閱方法
  4. 根據訂閱者的 threadMode 來判斷線程
  5. 經過反射直接調用訂閱者的訂閱方法來完成本次通訊息

Subscribe

這裏是訂閱者的意思,經過自定義註解實現

@Documented
@Retention(RetentionPolicy.RUNTIME) //註解會在class字節碼文件中存在,在運行時能夠經過反射獲取到
@Target({ElementType.METHOD}) //只能在方法上聲明
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING; //默認在 post 線程

    boolean sticky() default false; //默認不是粘性事件

    int priority() default 0; //線程優先級默認
}
複製代碼

threadMode

package org.greenrobot.eventbus;

public enum ThreadMode {
    POSTING, // post 線程
    MAIN,//指定主線程
    MAIN_ORDERED,
    BACKGROUND,//後臺進行
    ASYNC;//異步

    private ThreadMode() {
    }
}
複製代碼

unregister

流程:

EventBus-unRegister.png

EventBus 源碼總結

到這裏咱們就簡單的分析了 註冊 - > 訂閱 - > 發送 - >接收事件 簡單的流程就是這樣了,若是想更深刻的話,建議下載源碼來看 EventBus GitHub 架構方面仍是要注重基礎知識,好比 註解 + 反射 + 設計模式 (設計模式的話建議去看 《Android 源碼設計模式》一書 ),如今開源項目幾乎離不開這幾項技術。只有咱們掌握了基礎 + 實現原理。咱們模仿着也能寫出來一樣的項目。

下面咱們就簡單模仿下 EventBus 原理,實現本身的 EventBus 框架。

YEventBus 根據 TAG 實現接收消息架構實現

說明一點,這裏咱們仍是根據 EventBus 的核心原理實現,並不會有 EventBus 那麼多功能,咱們只是學習 EventBus 原理的同時,能根據它的原理,本身寫一套輕量級的 EventBus 架構。

最後一共差很少 300 行代碼實現根據 TAG 發送/接收事件,下面是效果圖:

如下我就直接貼代碼了 每一步代碼都有詳細的註釋,相信應該不難理解。

使用方式

  • 添加依賴

    allprojects {
      	repositories {
      		...
      		maven { url 'https://jitpack.io' }
      	}
      }
    
      dependencies {
              implementation 'com.github.yangkun19921001:YEventBus:Tag'
      }
    複製代碼
  • 註冊事件

    //開始註冊事件。模仿 EventBus
     YEventBus.getDefault().register(this);
    複製代碼
  • 訂閱消息

    /** * 這裏是自定義的註冊,最後經過反射來獲取當前類裏面的訂閱者 * * @param meg */
    @YSubscribe(threadMode = YThreadMode.MAIN, tag = Constants.TAG_1)
        public void onEvent(String meg) {
            Toast.makeText(getApplicationContext(), "收到:" + meg, Toast.LENGTH_SHORT).show();
        }
    複製代碼
  • 發送消息

    YEventBus.getDefault().post(Constants.TAG_1, "發送 TAG 爲 1 的消息");
    複製代碼

register

/** * 註冊方法 */
    public void register(Object subscriber) {
        //拿到當前註冊的全部的訂閱者
        List<YSubscribleMethod> ySubscribleMethods = mChacheSubscribleMethod.get(subscriber);
        //若是訂閱者已經註冊了 就不須要再註冊了
        if (ySubscribleMethods == null) {
            //開始反射找到當前類的訂閱者
            ySubscribleMethods = getSubscribleMethods(subscriber);
            //註冊了就存在緩存中,避免屢次註冊
            mChacheSubscribleMethod.put(subscriber, ySubscribleMethods);
        }
    }

    /** * 拿到當前註冊的全部訂閱者 * * @param subscriber * @return */
    private List<YSubscribleMethod> getSubscribleMethods(Object subscriber) {
        //拿到註冊的 class
        Class<?> subClass = subscriber.getClass();
        //定義一個容器,用來裝訂閱者
        List<YSubscribleMethod> ySubscribleMethodList = new ArrayList<>();
        //開始循環找到
        while (subClass != null) {
            //1. 開始進行篩選,若是是系統的就不須要進行下去
            String subClassName = subClass.getName();
            if (subClassName.startsWith(Constants.JAVA) ||
                    subClassName.startsWith(Constants.JAVA_X) ||
                    subClassName.startsWith(Constants.ANDROID) ||
                    subClassName.startsWith(Constants.ANDROID_X)
            ) {
                break;
            }
            //2. 遍歷拿到當前 class
            Method[] declaredMethods = subClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                //3. 檢測當前方法中是否有 咱們的 訂閱者 註解也就是 YSubscribe
                YSubscribe annotation = declaredMethod.getAnnotation(YSubscribe.class);
                //若是沒有直接跳出查找
                if (annotation == null)
                    continue;

                // check 這個方法的參數是否有多個
                Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                if (parameterTypes.length > 1) {
                    throw new RuntimeException("YEventBus 只能接收一個參數");
                }

                //4. 符合要求,最後添加到容器中
                //4.1 拿到須要在哪一個線程中接收事件
                YThreadMode yThreadMode = annotation.threadMode();
                //只能在當前 tag 相同下才能接收事件
                String tag = annotation.tag();
                YSubscribleMethod subscribleMethod = new YSubscribleMethod(tag, declaredMethod, yThreadMode, parameterTypes[0]);
                ySubscribleMethodList.add(subscribleMethod);

            }
            //去父類找訂閱者
            subClass =   subClass.getSuperclass();
        }
        return ySubscribleMethodList;
    }
複製代碼

post (這裏不是粘性事件)

/** * post 方法 */
    public void post(String tag, Object object) {
        //拿到當前全部訂閱者持有的類
        Set<Object> subscriberClass = mChacheSubscribleMethod.keySet();
        //拿到迭代器,
        Iterator<Object> iterator = subscriberClass.iterator();
        //進行循環遍歷
        while (iterator.hasNext()) {
            //拿到註冊 class
            Object subscribleClas = iterator.next();
            //獲取類中全部添加訂閱者的註解
            List<YSubscribleMethod> ySubscribleMethodList = mChacheSubscribleMethod.get(subscribleClas);
            for (YSubscribleMethod subscribleMethod : ySubscribleMethodList) {
                //判斷這個方法是否接收事件
                if (!TextUtils.isEmpty(tag) && subscribleMethod.getTag().equals(tag) //註解上面的 tag 是否跟發送者的 tag 相同,相同就接收
                        && subscribleMethod.getEventType().isAssignableFrom(object.getClass() //判斷類型
                )
                ) {
                    //根據註解上面的線程類型來進行切換接收消息
                    postMessage(subscribleClas, subscribleMethod, object);
                }

            }

        }

    }

    private void postMessage(final Object subscribleClas, final YSubscribleMethod subscribleMethod, final Object message) {

        //根據須要的線程來進行切換
        switch (subscribleMethod.getThreadMode()) {
            case MAIN:
                //若是接收的是主線程,那麼直接進行反射,執行訂閱者的方法
                if (isMainThread()) {
                    postInvoke(subscribleClas, subscribleMethod, message);
                } else {//若是接收消息在主線程,發送線程在子線程那麼進行線程切換
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            postInvoke(subscribleClas, subscribleMethod, message);
                        }
                    });
                }
                break;
            case ASYNC://須要在子線程中接收
                if (isMainThread())
                    //若是當前 post 是在主線程中,那麼切換爲子線程
                    ThreadUtils.executeByCached(new ThreadUtils.Task<Boolean>() {
                        @Nullable
                        @Override
                        public Boolean doInBackground() throws Throwable {
                            postInvoke(subscribleClas, subscribleMethod, message);
                            return true;
                        }

                        @Override
                        public void onSuccess(@Nullable Boolean result) {
                            Log.i(TAG, "執行成功");
                        }

                        @Override
                        public void onCancel() {

                        }

                        @Override
                        public void onFail(Throwable t) {

                        }
                    });
                else
                    postInvoke(subscribleClas, subscribleMethod, message);
                break;
            case POSTING:
            case BACKGROUND:
            case MAIN_ORDERED:
                postInvoke(subscribleClas, subscribleMethod, message);
                break;
            default:
                break;
        }
    }

    /** * 反射調用訂閱者 * * @param subscribleClas * @param subscribleMethod * @param message */
    private void postInvoke(Object subscribleClas, YSubscribleMethod subscribleMethod, Object message) {
        Log.i(TAG, "post message: " + "TAG:" + subscribleMethod.getTag() + " 消息體:" + message);
        Method method = subscribleMethod.getMethod();
        //執行
        try {
            method.invoke(subscribleClas, message);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
複製代碼

Subscribe 訂閱者

/** * <pre> * author : devyk on 2019-07-27 18:05 * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts * github : https://github.com/yangkun19921001 * mailbox : yang1001yk@gmail.com * desc : This is YSubscribe * </pre> */

@Target(ElementType.METHOD) //target 描述此註解在哪裏使用
@Retention(RetentionPolicy.RUNTIME) //retention 描述此註解保留的時長 這裏是在運行時
public @interface YSubscribe {
    YThreadMode threadMode() default YThreadMode.POSTING; //默認是在 post 線程接收數據

    String tag() default "";//根據消息來接收事件
}

複製代碼

TreadMode 線程模式

/** * <pre> * author : devyk on 2019-07-27 18:14 * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts * github : https://github.com/yangkun19921001 * mailbox : yang1001yk@gmail.com * desc : This is YThreadMode * </pre> */
public enum  YThreadMode {
    /** * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread. */
    POSTING,

    /** * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread. * If not on Android, behaves the same as {@link #POSTING}. */
    MAIN,

    /** * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN}, * the event will always be queued for delivery. This ensures that the post call is non-blocking. */
    MAIN_ORDERED,

    /** * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread. */
    BACKGROUND,

    /** * Subscriber will be called in a separate thread. This is always independent from the posting thread and the * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications. */
    ASYNC

}
複製代碼

unRegister取消註冊

/** * 取消註冊訂閱者 */
    public void unRegister(Object subscriber) {
        Log.i(TAG, "unRegister start:當前註冊個數" + mChacheSubscribleMethod.size());
        Class<?> subClas = subscriber.getClass();
        List<YSubscribleMethod> ySubscribleMethodList = mChacheSubscribleMethod.get(subClas);
        if (ySubscribleMethodList != null)
            mChacheSubscribleMethod.remove(subscriber);

        Log.i(TAG, "unRegister success:當前註冊個數" + mChacheSubscribleMethod.size());
    }

複製代碼

框架怎麼實現根據 TAG 接收消息

這個其實很簡單,拿到 post 發送的 tag,跟訂閱者的 tag 比較下就好了。其實只要瞭解原理也沒有那麼可貴。

//判斷這個方法是否接收事件
                if (!TextUtils.isEmpty(tag) && subscribleMethod.getTag().equals(tag) //註解上面的 tag 是否跟發送者的 tag 相同,相同就接收
                        && subscribleMethod.getEventType().isAssignableFrom(object.getClass() //判斷類型
                )
複製代碼

總結

最後咱們根據開源項目 EventBus 實現了本身 代碼傳送陣 YEventBus 框架,能夠根據 TAG 發送/接收消息。只要瞭解開源框架原理,根據本身需求改動原有框架或者實現本身的框架都不是太難,加油!

感謝

EventBus GitHub

相關文章
相關標籤/搜索