1.EventBus的介绍与使用
1.1常见的组件间的通信方式
- Intent:跳转+传参(局限性非常大)
- Handler:通常用来更新主线程UI,使用不当容易出现内存泄露
- Interface:接口回调,仅限于同一线程中数据交互
- BroadcastReceiver:有序广播+无序广播
- AIDL:跨进程通信,代码阅读性不友好,维护成本偏高
EventBus优点
- 代码简单,快
- Jar包小,~50k
- Activity,Fragment以及线程通信优秀
- 稳定,在一亿+应用中得到实践
1.2EventBus定义
EventBus是一个Android优化的publish/subscribe消息事件总线,简化了应用程序内各个组件间、组件与后台线程间的通信。如Activites、Fragments、Threads、Services
1.3使用场景
比如网络请求,返回时通过Handler或者BroadCastReceiver通知更新主线程UI
N个Fragment之间需要通过Listener(监听)通信
这些需求都可以通过EventBus完成和实现。可以在任何地方发布事件,任何地方订阅消费事件。完美的简化了跨组件、跨线程等方面通信的问题。
1.4官方架构图
1.5简单使用
-
导入EventBus库:implementation ‘org.greenrobot:eventbus:3.1.1’
-
注册和注销
@Override protected void onCreate() { super.onStart(); EventBus.getDefault().register(this); } @Override protected void onDestroy() { super.onStop(); if (EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().unregister(this); } }
-
官方三步曲
-
定义事件
public class MessageEvent { /*Additional fields if needed*/ }
-
订阅事件(注解+方法指定参数)
@Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { /*Do something*/ //监听到了发布的事件(消息) //进行相关操作,比如更新UI等 }
-
发步事件(发送潇潇)
EventBus.getDefault().post(new MessageEvent());
-
-
EventBus粘性事件(@Subscribe注解中加入:sticky = true 作用:延时消费或者初始化)
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true) public void onMessageEvent(UserInfo user) { Log.d(TAG, user.toString()); /*Do something*/ //监听到了发布的事件(消息) //进行相关操作,比如更新UI等 //如果请求接口然后再发送事件再进行控件的更新。有时候该控件所在的页面可能没有初始化好。 //这时候eventbus所发送的事件就不会起作用。这时候就要用到粘性事件。粘性事件可以先发送 //事件,待接收方订阅后接受事件。其实就是解决异步所带来的问题 }
-
订阅方法执行的优先级(如果两个或多个地方订阅同一事件,优先级高的先接收)
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1) public void onMessageEvent(UserInfo user) { //消费事件/消息 textView.setText(user.toString()); Log.d(TAG, "onMessageEvent"); } //priority数值越大,优先级越高,默认都为0 @Subscribe(threadMode = ThreadMode.MAIN, priority = 5) public void onMessageEvent(UserInfo user) { //消费事件/消息 textView.setText(user.toString()); Log.d(TAG, "onMessageEvent"); }
2.手写EventBus
2.1四个类实现EventBus
- ThreadMode:创建线程模式
- Subscribe:创建注解
- EventBus:封装方法类
- MethodManager:“存储”方法,并通过反射进行调用
2.1.1创建线程模式
public enum ThreadMode {
//事件的处理和事件的发送在相同的线程,所以事件处理时间不能太长,不然影响事件的发送线程,而这个线程可能是UI线程
POSTING,
//事件的处理在主线程中执行,事件处理不应耗时太长
MAIN,
//后台进程,处理如保存数据等操作
BACKGROUND,
//异步执行,另起线程操作,事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作
ASYNC
}
2.1.2创建注解
@Target(ElementType.METHOD)//作用在方法之上
@Retention(RetentionPolicy.RUNTIME)//jvm在运行时通过反射获取注解的值
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
}
2.1.3封装方法类
/**
* 保存符合要求订阅方法的封装类
*/
public class MethodManager {
//参数类型
private Class<?> type;
//线程模式
private ThreadMode threadMode;
//执行订阅方法
private Method method;
public MethodManager(Class<?> type, ThreadMode threadMode, Method method) {
this.type = type;
this.threadMode = threadMode;
this.method = method;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
public ThreadMode getThreadMode() {
return threadMode;
}
public void setThreadMode(ThreadMode threadMode) {
this.threadMode = threadMode;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "MethodManager{" +
"type=" + type +
", threadMode=" + threadMode +
", method=" + method +
'}';
}
}
2.1.4“存储”,方法,并通过反射进行调用
public class EventBus {
private static volatile EventBus instance;
//用来保存带注解方法(满足订阅条件的方法)
private Map<Object, List<MethodManager>> cacheMap;
private EventBus() {
cacheMap = new HashMap<>();
}
public static EventBus getDefault() {
if (instance == null) {
synchronized (EventBus.class) {
if (instance == null) {
instance = new EventBus();
}
}
}
return instance;
}
//收到信息方法
//找到MainActivity所有符合注解的方法
public void register(Object getter) {
List<MethodManager> methodList = cacheMap.get(getter);
if (methodList == null) {
methodList = findAnnotationMethod(getter);
//key代表MainActivity标识,value代表MainActivity中符合注解的方法的集合
cacheMap.put(getter, methodList);
}
}
/**
* 通过注解找到对应的方法
* @return
*/
private List<MethodManager> findAnnotationMethod(Object getter) {
List<MethodManager> methodList = new ArrayList<>();
Class<?> clazz = getter.getClass();
// Method[] methods = clazz.getDeclaredMethods();//获取当前类的所有方法
Method[] methods = clazz.getMethods();//获取当前类和父类的所有方法
//采用第一种方案就没有问题,采用第二种方案可以进一步优化
while (clazz != null) {
String clazzName = clazz.getName();
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
break;
}
for (Method method : methods) {
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe == null) {
continue;
}
//校验方法是否满足条件
//校验方法返回值类型
Type returnType = method.getGenericReturnType();
if (!"void".equals(returnType.toString())) {
throw new RuntimeException(method.getName() + "方法返回值类型必须是 void");
}
//校验方法参数
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new RuntimeException(method.getName() + "方法参数有且仅有一个");
}
//更多条件的校验
//...
//完全符合要求,添加到集合
MethodManager manager = new MethodManager(parameterTypes[0], subscribe.threadMode(), method);
methodList.add(manager);
}
//不断循环找出父类含有订阅方法,直至为空
clazz = clazz.getSuperclass();
}
return methodList;
}
//发送操作,发布事件
public void post(Object setter) {
Set<Object> set = cacheMap.keySet();
//获取MainActivity对象,即注册要发布消息的Activity或Fragment对象
for (Object getter : set) {
//获取MainActivity中所有带注解的方法
List<MethodManager> managerList = cacheMap.get(getter);
if (managerList != null) {
//循环执行每个订阅方法
for (MethodManager manager : managerList) {
//isAssignableFrom(用来匹配发布者和订阅者参数是否一致)
if (manager.getType().isAssignableFrom(setter.getClass())) {
invoke(manager, getter, setter);
}
}
}
}
}
/**
* 通过反射调用订阅(满足条件的带注解)的方法
*/
private void invoke(MethodManager manager, Object getter, Object setter) {
Method method = manager.getMethod();
try {
method.invoke(getter, setter);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
demo链接:https://github.com/mitufengyun/EventBusDemo