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泛型能不能實現參數泛型化而不用定義RegisterEvent
、LoginEvent
等一系列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泛型實現,我們來點擊註冊成功與登陸成功的結果是怎麼樣的?
發現無論是點擊註冊成功還是登陸成功,onLoginSuccess
與 onRegisterSuccess
都被調用了,爲什麼呢?
原來Java中的是僞泛型,在編譯的時候泛型 T 會被轉換爲 object 對象,所以無論你是GenericsEvent<LoginInfo>
還是GenericsEvent<RegisterInfo>
,編譯之後都是GenericsEvent類型,在GenericsEvent
中有一個object
對象來保存LoginInfo
或者RegisterInfo
,在獲取的時候,例如上面的getData()
,其實是那個object
對象強制轉化爲對應的T對象;若上述函數onLoginSuccess
和onRegisterSuccess
中調用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
來表示當前post
的GenericsEvent<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;
}
通過上面的修改,源碼基本上修改完了,下面需要去設置方法的註解以及post
的GenericsEvent<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的結果是一樣的,onLoginSuccess
與 onRegisterSuccess
都只被調用了一次,是正確無誤的。
通過修改EventBus
的源碼,使其支持參數泛型化,從此跟各種Event
類型說拜拜了!!!