EventBus之高效使用

Posted xyTianZhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);
            Android之EventBus使用

Android之EventBus使用详解

Guava之EventBus事件驱动。

RxBus-实现EventBus之Sticky

Android 第三方类库简单使用之EventBus

Android消息传递之EventBus 3.0