EventBus之高效使用

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

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值