EventBus框架总结之支持泛型参数
来源:互联网 发布:淘宝卖家客服转人工 编辑:程序博客网 时间:2024/05/21 05:18
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
类型说拜拜了!!!
- EventBus框架总结之支持泛型参数
- EventBus框架总结之用法
- EventBus框架总结之源码分析
- EventBus框架提炼总结
- 新框架 之 EventBus
- Android自助餐之EventBus框架
- Android 框架之EventBus(一)
- android开源框架之EventBus
- Android框架之EventBus的简单使用
- Android框架之EventBus的使用
- 软件框架之EventBus的使用
- EventBus框架
- 框架eventbus
- EventBus总结
- Android开源通信框架之——EventBus
- Android事件总线框架之EventBus(3.0为例)
- Android EventBus框架(一)之使用详细介绍
- Android EventBus框架(二)之源码简单解析
- java学习 jstl循环标签展示List集合操作
- ffmpeg Nvidia硬件加速总结
- CentOS 7 install Wordpress
- odoo 注意data_dir 这个参数
- java学习 jstl中forTokens标签的学习
- EventBus框架总结之支持泛型参数
- web系统国际化方案
- 如果maven修改了默认端口,但是启动的时候报8080倍占用,遇到的问题原因
- WebApp Ionic +webStorm开发 环境搭建
- python:爬虫系列-01
- Android自定义动画专题一
- 使用ajax或easyui等框架时的Json-lib的处理方案
- 强势文化与弱势文化
- centos7安装配置Nginx