EventBus的使用技巧和解析
一、EventBus简介
EventBus由greenrobot组织贡献(该组织还贡献了greenDAO),一个Android事件发布/订阅轻量级框架。
它是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间、组件与后台线程间的通信。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。
- EventBus原理框图
- 下面展示使用EventBus写的一个Demo
二、下载地址
三、使用步骤
1)添加jar包到libs文件夹下或者添加gradle依赖
2)注册:EventBus.getDefault().register(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
}
3)解注册:EventBus.getDefault().unregister(this);
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
4)构造发布消息类
public class MessageEvent {
public String msg;
public MessageEvent(String msg) {
this.msg = msg;
}
}
5)发布消息
EventBus.getDefault().post(new MessageEvent("我是刚过来的-.-"));
6)接收消息
@Subscribe(threadMode = ThreadMode.MAIN)
public void messageEventBus(MessageEvent event){
tv_result.setText(event.msg);
}
有四种模式的threadMode
- ThreadMode.MAIN 表示这个方法在主线程中执行
- ThreadMode.BACKGROUND 表示该方法在后台执行,不能并发处理
- ThreadMode.ASYNC 也表示在后台执行,可以异步并发处理
- ThreadMode.POSTING 表示该方法和消息发送方在同一个线程中执行
四、粘性事件
之前说的使用方法, 都是需要先注册(register), 再post,才能接受到事件;
如果你使用postSticky发送事件, 那么可以不需要先注册, 也能接受到事件.
1)构造发送信息类
public class StickyEvent {
public String msg;
public StickyEvent(String msg) {
this.msg = msg;
}
}
2)发布消息
EventBus.getDefault().postSticky(new StickyEvent("我是粘性消息~"));
3)接收消息
//注意,和之前的方法一样,只是多了一个 sticky = true 的属性.
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onEvent(StickyEvent event){
tv_c_result.setText(event.msg);
}
4)注册
EventBus.getDefault().register(CActivity.this);
5)解注册
EventBus.getDefault().removeAllStickyEvents();
EventBus.getDefault().unregister(CActivity.class);
五、源码解析
1)注册(订阅)源码分析
EventBus.getDefault().register(CActivity.this);
跟进getDefault()方法
public static EventBus getDefault() {
// 单例设计
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
// 可以看出来,EventBus是单例模式存在的,一个项目中只能有一个EventBus这样有利于管理订阅者和订阅方法,这会在下面的介绍中体现出来。
public EventBus() {
this(DEFAULT_BUILDER);
}
可以发现:EventBus采用双重校验锁设计为一个单例模式,奇怪的在于虽然设计为单例模式,但是构造方法确实public类型。EventBus默认支持一条事件总线,通常是通过getDefault()方法获取EventBus实例,但也能通过直接new EventBus这种最简单的方式获取多条事件总线,彼此之间完全分开。
接着,跟进register(Object subscriber)方法
// 1 注册
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
订阅方法实体类
// 1.1 订阅方法的实体类
public class SubscriberMethod {
final Method method; // 方法
final ThreadMode threadMode; // 执行线程
final Class<?> eventType; // 接收事件类型
final int priority; // 优先级
final boolean sticky; // 粘性事件
/** Used for efficient comparison */
String methodString;
public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) {
this.method = method;
this.threadMode = threadMode;
this.eventType = eventType;
this.priority = priority;
this.sticky = sticky;
}
//。。。。
}
从订阅类中获取所有的订阅方法信息
// 1.2 从订阅类中获取所有的订阅方法信息;
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 首先从缓存中读取
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
// 默认false
if (ignoreGeneratedIndex) {
// 利用反射来获取订阅类中的订阅方法信息
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 从注解器生成的类中获得订阅类的订阅方法信息
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
// 保存进缓存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
先看看通过反射来获取订阅方法信息的方法findUsingReflection()
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
// 取订阅类的方法信息
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
// 释放findState集合,并返回订阅类的方法
return getMethodsAndRelease(findState);
}
FindState其实就是一个里面保存了订阅者和订阅方法信息的一个实体类,包括订阅类中所有订阅的事件类型和所有的订阅方法等。
我们看到会首先创建一个FindState对象并执行findUsingReflectionInSingleClass(findState);来获取订阅类的方法信息
跟进findUsingReflectionInSingleClass(findState)方法
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
// 通过反射获取到订阅类中的所有方法
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
// 遍历所有方法,忽略private类型的,最后如果是公有,并且不是
// java编译器 生成的方法名,那么就是我们要的了。
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();
// 添加方法
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
可以看到,首先会得到订阅类的class对象并通过反射获取订阅类中的所有方法信息,然后通过筛选获取到订阅方法集合。
程序执行到此我们就获取到了订阅类中的所有的订阅方法信息
退回去再看看通过注解获取订阅方法信息findUsingInfo()
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
// 从FIND_STATE_POOL数组中查找FindState,命中返回,否则直接new
FindState findState = prepareFindState();
// 初始化FindState
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
// findState.subscriberInfo默认null
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
接着走注册订阅方法subscribe():
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 获取订阅方法的事件类型
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 根据订阅的事件类型获取所有的订阅者
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// 将订阅者添加到subscriptionsByEventType集合中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
// 根据优先级,将订阅者插入到指定的位置
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 获取订阅者所有订阅的事件类型
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
// 将该事件类型添加到typesBySubscriber中
subscribedEvents.add(eventType);
// 如果接收sticky事件,立即分发sticky事件
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
总结一下,注册EventBus主要干了几件事
- 首先获取订阅方法的参数类型即订阅事件类型
- 根据订阅事件类型获取该事件类型的所有订阅者
- 将该订阅者添加到该事件类型的订阅者集合中即:subscriptionsByEventType
- 获取订阅者所有的订阅事件类型
- 将该事件类型添加到该订阅者的订阅事件类型集中即:typesBySubscriber
附上流程图:
2)发布消息源码解析
EventBus.getDefault().post("发送普通数据");
getDefault方法跟上面注册源码中的getDefault方法是一样的,主要看post方法
public void post(Object event) {
// 获取当前线程的postingState
PostingThreadState postingState = currentPostingThreadState.get();
// 取得当前线程的事件队列
List<Object> eventQueue = postingState.eventQueue;
// 将该事件添加到当前的事件队列中等待分发
eventQueue.add(event);
if (!postingState.isPosting) {
// 判断是否是在主线程post
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
// 分发事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
3)取消注册源码分析
EventBus.getDefault().unregister(this);
跟进unregister()方法:
public synchronized void unregister(Object subscriber) {
//获取订阅者的所有订阅的事件类型
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
//从事件类型的订阅者集合中移除订阅者
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
六、总结
(1)EventBus总线接收消息四大线程
(2)粘性事件的特点
(3)注册源码分析
- 通过反射或注解获取所有的订阅方法
- 将当前订阅者添加到Eventbus总的事件订阅者的集合中
- 将当前订阅者所有订阅的事件类型添加到typeBySubscriber中
(4)发送源码分析
- 得到要发送的事件类型
- 根据事件类型获取所有订阅者,并循环向每个订阅者发送
(5)解注册源码分析
- 通过typeBySubscriber获取当前订阅者所有的事件类型
- 循环遍历每一个事件类型,并删除当前订阅者的订阅的方法
七、参考网站
EventBus3.0源码解析
Android EventBus3.0使用及源码解析
相关Demo可以去我github上的项目看看源码:
传送门