EventBus框架總結之支持泛型參數

EventBus框架總結之用法
EventBus框架總結之源碼分析


    前面兩篇對EventBus的使用以及實現源碼進行了總結,這一篇主要對EventBus源碼的修改實現支持泛型參數的總結。

EventBus也會有煩惱


    在EventBus框架總結之用法中介紹時提到,當系統登錄之後通過EventBus發送一個LoginEvent;在用戶註冊成功的時候,發送一個RegisterEvent;那用戶退出登錄時,同樣需要發送一個LogoutEvent事件;當應用規模發展比較大的時候,每次需要通過EventBus發送一個新的事件時,都需要新定義一個類,造成Event類的泛濫,可能就是下面這個樣子,

public class LoginEvent{}
public class LogoutEvent{}
public class RegisterEvent{}
...
可能中間有幾十上百個類
...
public class AppExitEvent{}

    對於大部分有代碼潔癖的人來說,遇到這樣的情況都是不能被接受的。這種情況下大家第一個會想到Java泛型,若要了解泛型原理,請查看Java泛型原理詳解

Java泛型真能實現嗎?


    我可以先告訴你,答案是不能夠實現,不然也沒有這篇文章的必要了。我們先來看看我們通常使用的情形,代碼走起

public class LoginInfo{
}
public class RegisterInfo{
}

 @Override
 public void onClick(View v) {
        int id  = v.getId();
        switch (id) {

            case R.id.login_success_text_view:
                loginSuccess();
                break;

            case R.id.register_success_text_view:
                registerSuccess();
                break;

            default:
                break;
        }
    }

    private void loginSuccess() {
        LoginEvent event = new LoginEvent();
        event.info = new LoginInfo();
        EventBus.getDefault().post(event);
    }

    private void registerSuccess() {
        RegisterEvent event = new RegisterEvent();
        event.info = new RegisterInfo();
        EventBus.getDefault().post(event);
    }

    @SuppressWarnings({"unused","登陸成功之後回調"})
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onLoginSuccess(LoginEvent event) {
        if(event.info != null) {
            Toast.makeText(this, event.info.getInfo(), Toast.LENGTH_SHORT).show();
        }
    }

    @SuppressWarnings({"unused","註冊成功之後回調"})
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onRegisterSuccess(RegisterEvent event) {
        if(event.info != null) {
            Toast.makeText(this, event.info.getInfo(), Toast.LENGTH_SHORT).show();
        }
    }

    PS:SuppressWarnings這個註解是我的強迫症才加入的,跟EventBus無關,因爲沒有直接引用的方法,在IDE裏面方法名稱會變成灰色;通過SuppressWarnings這個註解既可以讓方法名稱不變爲灰色,又可以對函數添加備註,以免被其他人以爲這個函數沒有被引用而不小心刪除。
上面的代碼現象如下,點擊註冊成功回調onRegisterSuccess進而彈出toast,點擊登陸成功回調onLoginSuccess彈出toast,一切如我們所想的,按照正常流程來走。
                這裏寫圖片描述

    那麼通過Java泛型能不能實現參數泛型化而不用定義RegisterEventLoginEvent等一系列event類呢?繼續代碼,新建一個GenericsEvent類來實現泛型

public class GenericsEvent<T> {

    private T mData;

    public void setData(T data) {
        mData = data;
    }

    public T getData() {
        return mData;
    }
}

接着,登陸與註冊成功之後通過包裝GenericsEvent來發送事件

   private void loginSuccess() {
        LoginInfo info = new LoginInfo();
        GenericsEvent<LoginInfo> genericsEvent = new GenericsEvent<>();
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    private void registerSuccess() {
        RegisterInfo info = new RegisterInfo();
        GenericsEvent<RegisterInfo> genericsEvent = new GenericsEvent<>();
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    @SuppressWarnings({"unused","登陸成功之後回調"})
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onLoginSuccess(GenericsEvent<LoginInfo> event) {
        Log.d("event","login success");

    }

    @SuppressWarnings({"unused","註冊成功之後回調"})
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onRegisterSuccess(GenericsEvent<RegisterInfo> event) {
        Log.d("event","register success");
    }

    通過Java泛型實現,我們來點擊註冊成功與登陸成功的結果是怎麼樣的?
                 這裏寫圖片描述

    發現無論是點擊註冊成功還是登陸成功,onLoginSuccessonRegisterSuccess都被調用了,爲什麼呢?
    原來Java中的是僞泛型,在編譯的時候泛型 T 會被轉換爲 object 對象,所以無論你是GenericsEvent<LoginInfo>還是GenericsEvent<RegisterInfo>,編譯之後都是GenericsEvent類型,在GenericsEvent中有一個object對象來保存LoginInfo或者RegisterInfo,在獲取的時候,例如上面的getData(),其實是那個object對象強制轉化爲對應的T對象;若上述函數onLoginSuccessonRegisterSuccess中調用getData()函數,會拋出強制轉化失敗的問題,因爲兩個函數都會被調用,而因爲泛型會強制轉化類型導致的。
    根據上面所有的,採用Java泛型GenericsEvent<T>,實際上對應的類型都是GenericsEvent,根據上一篇EventBus框架總結之源碼分析可以知道,EventBus是根據參數的類型來判斷是否回調,因爲採用EventBus.getDefault().post(genericsEvent);發佈事件,會導致所有的事件都被觸發,因此採用Java泛型是不能夠解決上面提到的類型氾濫的問題的。


如何解決Event類型氾濫的問題呢?


    既然Java泛型沒辦法解決這個問題,看來一般通用的方法是沒辦法解決這個問題了。那麼就只能從源碼上解決這個問題了。
    這時候想到EventBus的線程模型,還記得我們的線程模型是通過Subscribe這個註解來進行設置的,然後在post()中來判斷當前處於哪個線程以及Subscribe中設置的threadMode,從而進行對應的切換。那麼,既然通用的泛型沒法解決這個問題,那麼我們是不是也可以通過Subscribe來設置我們當前函數所能接受的GenericsEvent<T>T的類型,然後在post對應的GenericsEvent的時候,傳入對應的type來表示當前postGenericsEvent<T>T的類型,然後在EventBus中通過反射invoke函數之前來判斷GenericsEvent<T>中T的類型與當前函數通過Subscribe設置的類型是不是一樣,一樣就invoke對應的函數,不然就不invoke函數,不做處理。

且看代碼,在GenericsEvent<T>中傳入對應的type

public interface IGenericsEvent {
    Class<?> getGenericsType();
}

public class GenericsEvent<T> implements IGenericsEvent {
    //通過參數來記錄當前post的GenericsEvent中T的類型,
    //在invoke之前來與Subscribe中設置的type是否一致
    //來決定是否invoke函數
    private final Class<T> mType;

    public GenericsEvent(Class<T> type) {
        mType = type;
    }

    @Override
    public Class<?> getGenericsType() {
        return mType;
    }

}

既然要通過Subscribe來設置函數所接受的T的類型,那就需要修改Subscribe的源碼,增加genericsType參數

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    boolean sticky() default false;
    int priority() default 0;
    /**
     * 泛型類型,來表示當前函數接受哪種類型
     */
    Class<?> genericsType() default String.class;
}

通過EventBus框架總結之源碼分析中我們可以瞭解到讀取的註解中保存在SubscriberMethod中,所以也需要修改SubscriberMethod的代碼來保存genericsType,因此也添加genericsType參數,同時修改它的構造函數

/** Used internally by EventBus and generated subscriber indexes. */
public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    /**
     * 泛型類型,來表示當前函數接受哪種類型
     */
    final Class<?> genericsType;
    final int priority;
    final boolean sticky;
    String methodString;

    public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky,Class<?> genericsType) {
        this.method = method;
        this.threadMode = threadMode;
        this.eventType = eventType;
        this.priority = priority;
        this.sticky = sticky;
        this.genericsType = genericsType;
    }
    /..省略無關代碼../
}

然後再通過findUsingReflectionInSingleClass()函數去讀取通過Subscribe註解設置的genericsType保存到SubscriberMethod

private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        /..省略無關的代碼../
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            //讀取註解設置的類型
                            Class<?> genericsType = subscribeAnnotation.genericsType();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky(), genericsType));
                        }
                    }
                } 
                /..省略無關的代碼../
            }
        }
    }

通過上面的代碼修改,通過Subscribe設置的函數支持的參數類型,接下來就是要在invoke函數之前對類型參數讀取並比較,只有當類型一樣的時候才invoke進行調用,EventBus框架總結之源碼分析中分析了post通過一層層調用之後調用到postSingleEventForEventType(),然後postSingleEventForEventType()調用postToSubscription(),而postToSubscription()函數的主要作用是實現EventBus的線程模型切換線程的功能,爲了避免不必要的線程切換,因爲在切換線程之前進行判斷攔截

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        //線程切換之前進行攔截
        if(!needInvokeSubscriber(subscription, event)) {
            return;
        }
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(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);
        }
    }
    /**
     * 判斷是否需要攔截invoke Subscriber
     */
    private boolean needInvokeSubscriber(Subscription subscription, Object event) {
        //首先判斷當前的event是否是泛型,若不是泛型類型,則不能
        //攔截事件,需要invoke
        if(!(event instanceof IGenericsEvent)) {
            return true;
        }
        //若當前event爲泛型類型,則讀取event中設置的genericsType
        IGenericsEvent genericsEvent = (IGenericsEvent) event;
        Class<?> genericClass = genericsEvent.getGenericsType();
        //與當前函數通過Subscribe註解設置的genericsType類型是否一致,
        //當一致是需要invoke,否則進行攔截
        return subscription.subscriberMethod.genericsType == genericClass;
    }

通過上面的修改,源碼基本上修改完了,下面需要去設置方法的註解以及postGenericsEvent<T>時候設置對應的類型genericsType

 private void loginSuccess() {
        LoginInfo info = new LoginInfo();
        //通過構造函數注入LoginInfo的類型
        GenericsEvent<LoginInfo> genericsEvent = new GenericsEvent<>(LoginInfo.class);
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    private void registerSuccess() {
        RegisterInfo info = new RegisterInfo();
        //通過構造函數注入RegisterInfo的類型
        GenericsEvent<RegisterInfo> genericsEvent = new GenericsEvent<>(RegisterInfo.class);
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    //通過Subscribe註解設置genericsType=LoginInfo,即當前函數支持登陸
    //LoginInfo類型,其他類型不會回調該函數
    @SuppressWarnings({"unused","登陸成功之後回調"})
    @Subscribe(threadMode = ThreadMode.MAIN,genericsType = LoginInfo.class)
    public void onLoginSuccess(GenericsEvent<LoginInfo> event) {
        Toast.makeText(this, "login success", Toast.LENGTH_SHORT).show();
    }

    //通過Subscribe註解設置genericsType=RegisterInfo,即當前函數支持
    //註冊RegisterInfo類型,其他類型不會回調該函數
    @SuppressWarnings({"unused","註冊成功之後回調"})
    @Subscribe(threadMode = ThreadMode.MAIN,genericsType = RegisterInfo.class)
    public void onRegisterSuccess(GenericsEvent<RegisterInfo> event) {
        Toast.makeText(this, "register success", Toast.LENGTH_SHORT).show();
    }

通過上述修改,我們看看運行的具體結果如何
                這裏寫圖片描述

上述的結果與最開始的通過定義各種Event的結果是一樣的,onLoginSuccessonRegisterSuccess都只被調用了一次,是正確無誤的。

通過修改EventBus的源碼,使其支持參數泛型化,從此跟各種Event類型說拜拜了!!!

發佈了29 篇原創文章 · 獲贊 16 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章