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泛型原理详解。
我可以先告诉你,答案是不能够实现,不然也没有这篇文章的必要了。我们先来看看我们通常使用的情形,代码走起
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 {
private T mData;
public void setData(T data) {
mData = data;
}
public T getData() {
return mData;
}
}
接着,登陆与注册成功之后通过包装GenericsEvent
来发送事件
private void loginSuccess() {
LoginInfo info = new LoginInfo();
GenericsEvent genericsEvent = new GenericsEvent<>();
genericsEvent.setData(info);
EventBus.getDefault().post(genericsEvent);
}
private void registerSuccess() {
RegisterInfo info = new RegisterInfo();
GenericsEvent genericsEvent = new GenericsEvent<>();
genericsEvent.setData(info);
EventBus.getDefault().post(genericsEvent);
}
@SuppressWarnings({"unused","登陆成功之后回调"})
@Subscribe(threadMode = ThreadMode.MAIN)
public void onLoginSuccess(GenericsEvent event) {
Log.d("event","login success");
}
@SuppressWarnings({"unused","注册成功之后回调"})
@Subscribe(threadMode = ThreadMode.MAIN)
public void onRegisterSuccess(GenericsEvent event) {
Log.d("event","register success");
}
通过Java泛型实现,我们来点击注册成功与登陆成功的结果是怎么样的?
发现无论是点击注册成功还是登陆成功,onLoginSuccess
与 onRegisterSuccess
都被调用了,为什么呢?
原来Java中的是伪泛型,在编译的时候泛型 T 会被转换为 object 对象,所以无论你是GenericsEvent
还是GenericsEvent
,编译之后都是GenericsEvent类型,在GenericsEvent
中有一个object
对象来保存LoginInfo
或者RegisterInfo
,在获取的时候,例如上面的getData()
,其实是那个object
对象强制转化为对应的T对象;若上述函数onLoginSuccess
和onRegisterSuccess
中调用getData()
函数,会抛出强制转化失败的问题,因为两个函数都会被调用,而因为泛型会强制转化类型导致的。
根据上面所有的,采用Java泛型GenericsEvent
,实际上对应的类型都是GenericsEvent
,根据上一篇EventBus框架总结之源码分析可以知道,EventBus
是根据参数的类型来判断是否回调,因为采用EventBus.getDefault().post(genericsEvent);
发布事件,会导致所有的事件都被触发,因此采用Java泛型是不能够解决上面提到的类型泛滥的问题的。
既然Java泛型没办法解决这个问题,看来一般通用的方法是没办法解决这个问题了。那么就只能从源码上解决这个问题了。
这时候想到EventBus
的线程模型,还记得我们的线程模型是通过Subscribe
这个注解来进行设置的,然后在post()
中来判断当前处于哪个线程以及Subscribe中设置的threadMode
,从而进行对应的切换。那么,既然通用的泛型没法解决这个问题,那么我们是不是也可以通过Subscribe
来设置我们当前函数所能接受的GenericsEvent
中T
的类型,然后在post
对应的GenericsEvent
的时候,传入对应的type
来表示当前post
的GenericsEvent
的T
的类型,然后在EventBus
中通过反射invoke
函数之前来判断GenericsEvent
中T的类型与当前函数通过Subscribe
设置的类型是不是一样,一样就invoke
对应的函数,不然就不invoke
函数,不做处理。
且看代码,在GenericsEvent
中传入对应的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
时候设置对应的类型genericsType
private void loginSuccess() {
LoginInfo info = new LoginInfo();
//通过构造函数注入LoginInfo的类型
GenericsEvent genericsEvent = new GenericsEvent<>(LoginInfo.class);
genericsEvent.setData(info);
EventBus.getDefault().post(genericsEvent);
}
private void registerSuccess() {
RegisterInfo info = new RegisterInfo();
//通过构造函数注入RegisterInfo的类型
GenericsEvent 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 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 event) {
Toast.makeText(this, "register success", Toast.LENGTH_SHORT).show();
}
上述的结果与最开始的通过定义各种Event的结果是一样的,onLoginSuccess
与 onRegisterSuccess
都只被调用了一次,是正确无误的。
通过修改EventBus
的源码,使其支持参数泛型化,从此跟各种Event
类型说拜拜了!!!