EventBus之高效使用

    技术2022-07-11  85

    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 源码。

    Processed: 0.013, SQL: 9