EventBus之高效使用
说起 EventBus,作为一名 Android 开发者,应该不会太陌生,但是我们大部分都会根据官方文档直接进行使用,其实还有一种比较高效的使用方式。就是不使用注解的方式,在编译时期,对相关注册方法进行注册。
这其实就相当于用空间换时间的一种常规操作了。这里附上 官方源码 和 官方文档 的地址。
先来贴一张官方文档中的图解,让大家对 EventBus 的工作机制现有一个宏观上的回忆。
常规使用
先来看看常规的使用方式。
1、接入EventBus
implementation 'org.greenrobot:eventbus:3.2.0'
2、使用
//1、创建订阅事件
class DataModel(val msg: String)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//2、注册订阅
EventBus.getDefault().register(this)
}
//3、创建接收发布的事件的方法
@Subscribe(threadMode = org.greenrobot.eventbus.ThreadMode.MAIN, sticky = true, priority = 2)
fun dataReceive(dataModel: DataModel) {
Toast.makeText(this, dataModel.msg, Toast.LENGTH_LONG).show()
}
fun post(view: View) {
//4、发布事件
EventBus.getDefault().post(DataModel("send success"))
}
override fun onDestroy() {
//5、取消订阅
EventBus.getDefault().unregister(this)
super.onDestroy()
}
}
使用方式很简单,基本上和官方介绍的一样,为了方便就在这里聚合了一下。
下面来分析这种方式,是怎么实现订阅者与发布者之间进行信息交互的。
常规使用之源码分析
注册订阅者
在分析具体源码之前,先来看看几个相关的辅助类
1、Subscribe 作用于订阅事件方法的注解类
//运行时
@Retention(RetentionPolicy.RUNTIME)
//目标作用于方法
@Target({ElementType.METHOD})
public @interface Subscribe {
//线程模式,有当线程、主线程、子线程、异步线程等。
ThreadMode threadMode() default ThreadMode.POSTING;
//是否为粘性事件,就是可以先进行发布,后进行注册,注册时候会立刻收到发布的事件
boolean sticky() default false;
//接受的优先级,数值越大优先级越高
int priority() default 0;
}
2、Subscription、SubscriberMethod 订阅方法的封装类
final class Subscription {
//订阅的对象,例如上面的 MainActivity 对象(这里需要注意,不是类,而是具体的对象)
final Object subscriber;
//注册类中的订阅方法(与对象无关,解析相关类中的订阅方法)
final SubscriberMethod subscriberMethod;
}
public class SubscriberMethod {
//订阅的方法名
final Method method;
//执行的线程环境
final ThreadMode threadMode;
//订阅事件类型(订阅方法中的参数类型)
final Class<?> eventType;
//优先级
final int priority;
//是否为粘性事件
final boolean sticky;
//用于比较两个方法是否相同,具体在继承中会用到
String methodString;
}
基于上面的使用方法,我们可以直接从第二步的注册订阅事件的 register()
方法来进行源码的跟踪。
先来看看注册事件
public class EventBus {
//订阅方法的集合:<订阅类型,该类型的多有方法集合>,方便用于事件分发
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//订阅方法的集合:<订阅者对象,该类中的所有注册方法>,方便用于去掉订阅
private final Map<Object, List<Class<?>>> typesBySubscriber;
//所有的粘性事件集合:<订阅事件类型,事件对象>,用于后续新事件注册后的粘性事件分发
private final Map<Class<?>, Object> stickyEvents;
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
//从注册类中找出所有订阅事件的相关方法
//下文在分析 SubscriberMethodFinder 这个辅助类,这里先跳过,看注册的主流程
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//拿到当前订阅事件type的订阅集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
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);
}
//将订阅事件添加到订阅对象的集合中
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
......
//如果是粘性事件,则判断是否需要进行派发
//这也就是为什么可以先发布事件,后面进行注册也可以收到发布事件的原因了
//每次新注册方法时都会判断是否需要进行粘性事件的分发
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
......
}
}
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
......
invokeSubscriber(subscription, event)
......
}
void invokeSubscriber(Subscription subscription, Object event) {
try {
//使用反射进行相应事件的调用
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
}
下面来看下上文中略过的 SubscriberMethodFinder
辅助类。
这个类的作用就是从传入的注册类中找出订阅方法的集合并返回。
class SubscriberMethodFinder {
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
//从缓存中查找是否曾经注册过,如果有则直接返回
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
//这里就是上文中注册方法调用的方法,根据订阅的 class 寻找该类中的所有订阅方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
//使用反射寻找,由于没有进行任何相关配置,所以默认会使用反射进行查找,所以效率相对来说比较底下
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//使用添加的信息寻找
//这里后文分析高效使用时,在进行分析
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
//如果注册的类中没有订阅方法,则直接抛出异常。这就有点坑,容易出bug
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;
}
}
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//循环遍历当前类和父类,通过反射查找相应的订阅方法
while (findState.clazz != null) {
//查找当前类的订阅方法
findUsingReflectionInSingleClass(findState);
//查找完毕后,将当前 class 定位到父类
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 拿到该类中的所有方法,不包括父类的方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
try {
// 拿到该类中的所有方法,包括父类的方法
methods = findState.clazz.getMethods();
} catch (LinkageError error) {
......
}
findState.skipSuperClasses = true;
}
// 遍历所有方法,找到符合目标的订阅方法
for (Method method : methods) {
int modifiers = method.getModifiers();
// 校验方法的权限,只能是 public 的
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
// 并且该方法的参数只能为一个
if (parameterTypes.length == 1) {
// 拿到该方法的 Subscribe 注解属性,如果没有则不是订阅方法
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)) {
......
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
......
}
}
}
}
到这里,注册的流程基本上就完了。上面的注释写的基本上很详细了,也是从上到下的一个流程。
发布订阅事件
注册完成后,就是接受订阅事件了,以发布事件为入口,看看事件发布后,订阅的方法是如何接受到发布的订阅事件。
public class EventBus {
public void post(Object event) {
//用于判断在当前线程的分发状态
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 将发布事件加入队列中
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
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;
}
}
}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
......
postSingleEventForEventType(event, postingState, eventClass);
......
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 根据事件的类型,拿到所有订阅该事件的方法
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
......
// 遍历所有订阅该事件的方法,并进行分发
postToSubscription(subscription, event, postingState.isMainThread);
......
}
return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
......
// 这里会进行相应的线程切换,就不做过多解析
invokeSubscriber(subscription, event)
......
}
void invokeSubscriber(Subscription subscription, Object event) {
try {
//使用反射进行相应事件的调用
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
}
发布事件到这就结束了,就是从订阅的集合中拿到订阅的相关方法并进行调用,就完事了。
解注册订阅者
最后就是解注册了,相比分发就更为简单了,没什么说的,就是从集合中找出,删除完事。
public class EventBus {
public synchronized void unregister(Object subscriber) {
// 拿到解注册类中订阅的所有方法
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
// 遍历所有方法,根据事件类型,从 subscriptionsByEventType 集合中移除相应的订阅事件
unsubscribeByEventType(subscriber, eventType);
}
// 移除
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
}
以上就是我们正常使用 EventBus 的全过程解析了,有没有豁然开朗。其实原理并没有多么深奥,只要静下心看看就可以理解。更多需要注意的就是那些异常的处理以及细节的考虑了,这些才是这个项目的精华所在。
高效使用
既然题目是高效使用,那这部分才是要说的重点了,上面哪些相信大家都已经耳熟能详了,主要是总结一下。
在介绍使用之前,先看看一组数据对比,根据官方作者自述,这种方式可以进一步提升 app 的运行效率。因为在其编译时期就为其建立了一份索引库,而不是在运行时才去建立。相当于一种空间换时间,来提高 app 的运行效率
下面来看看怎么使用
1、在项目根目录中的 build
文件中新增 android-apt
插件引用
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
2、在项目的 build
文件中使用插件
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg('eventBusIndex', 'com.silence.eventbusdemo.EventBusIndex')
}
}
dependencies {
implementation 'org.greenrobot:eventbus:3.2.0'
kapt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
配置完成后点击 Build -> Rebuild Project 。重新 build 项目后,会在 build/generated/source/kapt/debug/com/silence/eventbusdemo
目录下生成一个 EventBusIndex
类。
这个类比较简单。就是在编译期间找到所有的订阅方法,然后构造相关订阅方法的信息,保存起来。在进行事件发布时,直接从这里面找就可以了。
public class EventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("dataReceive", DataModel.class, ThreadMode.MAIN, 2, true),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
3、在 Application 中引用生成的类
//别忘了在 manifest 文件中声明 aplication
class DemoApplication: Application() {
override fun onCreate() {
super.onCreate()
EventBus.builder().addIndex(EventBusIndex()).installDefaultEventBus()
}
}
4、使用和普通的正常使用一样,这里就不赘述了
高效使用之源码分析
还记得上面跳过的那个获取订阅事件方法吗,有一处没有分析,这里就分析一下。
class SubscriberMethodFinder {
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
if (ignoreGeneratedIndex) {
//使用反射寻找,由于没有进行任何相关配置,所以默认会使用反射进行查找,所以效率相对来说比较底下
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//使用添加的信息寻找,相关配置完成后会走到这里
subscriberMethods = findUsingInfo(subscriberClass);
}
}
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
......
while (findState.clazz != null) {
// 从编译时添加的订阅信息中查找出该类的订阅方法
findState.subscriberInfo = getSubscriberInfo(findState);
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);
}
private SubscriberInfo getSubscriberInfo(FindState findState) {
......
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
// 根据订阅类,从编译时生成的集合中找出订阅方法的信息
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
......
return null;
}
}
到这,普通版和进化版就介绍完毕了。有兴趣的同学可以看看那个插件是怎么生成相关类的,这里就不贴了,官网源码上就有。
手撸简易版 CustomEventBus
为了加深印象,这里 100 多行手撸了一个 简易版 CustomEventBus。仅供学习,不能再项目中使用昂。
这里在附上 CustomeEventBus 源码。