美文网首页
Android EventBus3.0+ 简单使用及源码讲解

Android EventBus3.0+ 简单使用及源码讲解

作者: ZuYuan | 来源:发表于2019-03-21 19:40 被阅读0次

    前言

    EventBus3.0之后添加了一项新功能——订阅者索引,该功能不是强制使用的,若是我们只导入implementation 'org.greenrobot:eventbus:3.1.1'是不能够使用索引的。该功能是在项目编译时生成索引文件,项目运行时执行这些文件,以达到提高运行速度的目的,因为在不使用索引功能情况下,使用的是反射,因此要耗时一些。
    本文就主要分析EventBus在不使用索引功能的情况下的源码,最后稍微分析下索引功能的实现。

    EventBus使用

    方式
    //发送对象
    //Object obj = ...
    
    //直接发送
    EventBus.getDefault().post(obj)
    //发送粘性事件
    EventBus.getDefault().postSticky(obj)
    

    以上是发送事件的方法,常见就这两种使用方式,可在任意线程使用。

    //对象初始化时-注册
    EventBus.getDefault().register(this)
    //对象销毁时-解绑
    EventBus.getDefault().unregister(this)
    
    //实现一个方法,该方法接收想要的Event类型
    //这里的MessageEvent自己定义的一种Event类型
    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    fun onRecived(even: MessageEvent) {
        tv_recived.text = even.message
    }
    

    这就是接收事件的使用方法,自定义一个方法,方法名任意,但是参数类型必须是我们想要接收的数据的类或者接口类型,再使用EventBus提供的注解去注释该方法:

    • threadMode: 定义接收线程模型,例如MAIN是指当前方法在主线程中执行,默认是POSTING指与调用者保持在同一线程中(保证开销最小)。
    • sticky:是否是粘性事件,即订阅者是否可以延时接收。
    • priority:接收的优先级。
    注意
    1. 每次注册、使用之后,一定要注销。例如在一个活动中注册了,若未注销,那么在这个活动进入onStop()之后,仍然能够接收消息(当然包括粘性消息),若此时更新UI,就可能引发程序崩溃。
    2. 发送事件之后,所有接收参数类型跟该事件类型相同的订阅方法都会被执行。

    源码分析

    订阅者注册

    我们注意到我们订阅的方法是EventBus实现调用的,那它肯定要收集订阅的方法,因此我们从EventBus.register(this)注册开始分析源码,先弄懂它的注册的机制是怎样的。

        public void register(Object subscriber) {
            Class<?> subscriberClass = subscriber.getClass();
            List<SubscriberMethod> subscriberMethods = subscriberMethodFinder
                                                      .findSubscriberMethods(subscriberClass);
            synchronized (this) {
                for (SubscriberMethod subscriberMethod : subscriberMethods) {
                    subscribe(subscriber, subscriberMethod);
                }
            }
        }
    

    这几句代码很清楚:
    首先找到订阅者的所有订阅方法,放入List集合中,这里使用了SubscriberMethod对象来表示订阅方法,里面包含了methodeventType等字段,这里的eventType就是我们发送事件的对象类型,例如上面的MessageEvent类型。
    然后是订阅List中的方法,在这个过程中要检查粘性事件是否发送给订阅者(发送粘性事件的实现)

    接着往下看,我们要找到订阅方法,需要去到SubscriberMethodFinder这个类中去看,它的findSubscriberMethods()方法:

        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()) {
                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;
            }
        }
    

    上面的代码就是使用索引查找和不使用索引查找,默认是使用索引查找。下面是findUsingInfo()方法

        private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
            //初始化FindState对象
            FindState findState = prepareFindState();
            findState.initForSubscriber(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();
            }
            //生成方法list,回收FindState对象
            return getMethodsAndRelease(findState);
        }
    

    先介绍下新出现的FindState类,它就是一个中间变量,最后是会被回收的,当中主要包含着:

            //就是作为订阅者及其订阅方法的信息
            final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
            final Map<Class, Object> anyMethodByEventType = new HashMap<>();
            final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
            final StringBuilder methodKeyBuilder = new StringBuilder(128);
    
            Class<?> subscriberClass;
            Class<?> clazz;
            boolean skipSuperClasses;
            SubscriberInfo subscriberInfo;
    

    而SubscriberMethod就是一个订阅方法类:

        //包含订阅方法的方法体、注解内容、参数类型
        final Method method;
        final ThreadMode threadMode;
        final Class<?> eventType;
        final int priority;
        final boolean sticky;
        String methodString;
    

    回到上面的方法中去,在索引功能不可用的情况下是进行findUsingReflectionInSingleClass(findState)方法:

    private void findUsingReflectionInSingleClass(FindState findState) {
            Method[] methods;
            try {
                //反射拿到方法
                methods = findState.clazz.getDeclaredMethods();
            } catch (Throwable th) {
                //失败则使用另外一种方式获取
                methods = findState.clazz.getMethods();
                //跳过父类检索
                findState.skipSuperClasses = true;
            }
            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的容器
                                findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                        subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                            }
                        }
                    } 
                }
            }
        }
    

    反射不用多说,这里的checkAdd()方法主要是FindState内部自己的检查,是否能够添加进去,就不贴代码了。最终实现的是将订阅方法全部放入FindState内部的subscriberMethods容器。

    再次回到上面的findUsingInfo()方法中去,它的代码还没有执行完,应该执行到最后一步getMethodsAndRelease(findState)

        private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
            //将订阅方法拿出
            List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
            //释放findState,插入数组
            findState.recycle();
            synchronized (FIND_STATE_POOL) {
                for (int i = 0; i < POOL_SIZE; i++) {
                    if (FIND_STATE_POOL[i] == null) {
                        FIND_STATE_POOL[i] = findState;
                        break;
                    }
                }
            }
            return subscriberMethods;
        }
    

    这个方法重要的地方在于findState的释放和回收,POOL_SIZE默认是4,那么之前findState的初始化也是优先考虑FIND_STATE_POOL中不为空元素。
    到此,我们终于拿到了订阅者中的所有订阅方法,接下来应该回到最上面的register()方法中,继续往下看for (SubscriberMethod subscriberMethod : subscriberMethods) subscribe(subscriber, subscriberMethod);将每一个方法进行订阅:

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            Class<?> eventType = subscriberMethod.eventType;
            //生成订阅者
            Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
            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);
    
            //针对粘性事件,是否post事件出去
            if (subscriberMethod.sticky) {
                //事件类型是否有继承类型
                if (eventInheritance) {
                    Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                    for (Map.Entry<Class<?>, Object> entry : entries) {
                        Class<?> candidateEventType = entry.getKey();
                        //若事件类型是key类型或者父类类型的话
                        if (eventType.isAssignableFrom(candidateEventType)) {
                            Object stickyEvent = entry.getValue();
                            //post 实现粘性事件
                            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                        }
                    }
                } else {
                    Object stickyEvent = stickyEvents.get(eventType);
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        }
    

    这一大段代码实现的功能很简单,就是将订阅方法投入EventBus的订阅方法容器中去,并在最后判断粘性事件则post一次。
    这里的CopyOnWriteArrayList是一种可在并发条件下操作的容器,它在每次操作时都会将数组拷贝一份出去,进行操作,所以它在并发条件下不用加锁。
    最后的粘性事件的实现其实很简单:在当前的对象实现订阅者注册时,EventBus检测到某个注册方法是允许接收粘性事件,它就调用stickyEvents.get(eventType);拿到对应事件类型的事件,并单独为这个newSubscriptionpost一次,将该事件单独发送给它,也就是checkPostStickyEventToSubscription(newSubscription, stickyEvent)这句代码。

    发起者post

    先讲postSticky(),因为它就比post()方法多了一行代码stickyEvents.put(event.getClass(), event),当然是加了线程锁的,就是将该事件投递进粘性事件的容器里面去。
    再讲post()方法:

    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;
                }
            }
        }
    

    这里新出现了一个PostingThreadState类,先说它获取的方式currentPostingThreadState.get(),其实postingState是放在了ThreadLocal中去,作为当前线程独有的一个变量,我们看下这个变量里面有什么:

            //就只有这几个属性
            final List<Object> eventQueue = new ArrayList<>();
            boolean isPosting;
            boolean isMainThread;
            Subscription subscription;
            Object event;
            boolean canceled;
    

    可见,在每一个线程中都有一个“事件队列”,每一次发送都会将该队列清空。并且每一个线程只有能
    接下来再看postSingleEvent()方法的实现

      private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
            Class<?> eventClass = event.getClass();
            boolean subscriptionFound = false;
            //事件是否使用继承检查
            if (eventInheritance) {
                //查找事件的所有类型(接口、父类)
                List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
                int countTypes = eventTypes.size();
                for (int h = 0; h < countTypes; h++) {
                    Class<?> clazz = eventTypes.get(h);
                    //依次发送事件
                    subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
                }
            } else {
                subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
            }
            if (!subscriptionFound) {
                if (logNoSubscriberMessages) {
                    logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
                }
                if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                        eventClass != SubscriberExceptionEvent.class) {
                    post(new NoSubscriberEvent(this, event));
                }
            }
        }
    

    进入postSingleEventForEventType()方法,就不贴它的源码了,从它再次进入postToSubscription(subscription, event, postingState.isMainThread);:

      private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
            switch (subscription.subscriberMethod.threadMode) {
                case POSTING:  //同一线程执行订阅方法
                    invokeSubscriber(subscription, event);
                    break;
                case MAIN:  //在主线程实现
                    if (isMainThread) {
                        invokeSubscriber(subscription, event);
                    } else {
                        mainThreadPoster.enqueue(subscription, event);
                    }
                    break;
                case MAIN_ORDERED:  //进入在主线程的事件队列中去,以实现事件串行
                    if (mainThreadPoster != null) {
                        mainThreadPoster.enqueue(subscription, event);
                    } else {
                        invokeSubscriber(subscription, event);
                    }
                    break;
                case BACKGROUND:  //保证在后台线程实现订阅方法
                    if (isMainThread) {
                        backgroundPoster.enqueue(subscription, event);
                    } else {
                        invokeSubscriber(subscription, event);
                    }
                    break;
                case ASYNC:  //在线程池中实现
                    asyncPoster.enqueue(subscription, event);
                    break;
                default:
                    throw new IllegalStateException("Unknown thread mode: "
                                   + subscription.subscriberMethod.threadMode);
            }
    

    一目了然,根据订阅方法需要的不同来在不同的环境下实现它。

    订阅方法的实现

    上面的invokeSubscriber(subscirption, event)方法是通过反射实现订阅方法:

    //参数一:订阅者类  参数二:事件对象
    subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    

    我们再根据threadMode的值来看不同情况下,订阅方法的实现:

    1. MAIN和MAIN_ORDERED
    它们的实现都主要是依靠mainThreadPoster,将event投递进mainThreadPoster的队列中去。看一下mainThreadPoster的由来:

          //这里的looper自然就是主线程的looper
          public Poster createPoster(EventBus eventBus) {
                return new HandlerPoster(eventBus, looper, 10);
            }
    

    这里的HandlerPoster是一个自定义的类,它有许多细节实现,就不再贴代码了。简述一下,它就是继承自Handler(绑定主线程的Looper),每次插入消息的时候,就使用handler.sendMessage(), handler.handleMessage()这两个方法来执行订阅方法实现的步骤。

    2. BACKGROUND
    它的实现主要是依靠backgroundPoster,也是同样执行enqueue()方法来将该事件插入队列,backgroundPoster是直接实例出来的,它的类实现接口Runnnable,类中主要实现代码:

         eventBus.getExecutorService().execute(this);
    

    借助EventBus中的线程池来执行自身的run()方法,而该线程池是newCachedThreadPool(),就是线程可复用,没啥特殊的。backgroundPosterrun()方法想必都能够猜到了,就是依次从事件队列中拿取事件并执行订阅方法,但是依旧有部分细节代码,就不贴了。


    3. ASYNC
    它的实现主要是依靠asyncPoster,它跟backgroundPoster的实现效果差不多,只是backgroundPoster保证事件是根据订阅方法优先级依次执行的,而asyncPoster将所有订阅方法一起执行(线程池的特性)。

    自定义EventBus——Builder

    我们常用的是EventBus.getDefault()拿到EventBus,但是也可以使用EventBus.Builder().build()来自定义我们的EventBus,下面罗列出Builder的主要方法:

    //关于Exception的方法就不罗列了
        
        //是否检查事件继承(事件类的父类、接口),关乎是否收集其父类的订阅方法,默认true
        public EventBusBuilder eventInheritance(boolean eventInheritance) {
            this.eventInheritance = eventInheritance;
            return this;
        }
      
        //自定义线程池,如上面所说,另外两种threadMode都使用该线程池
        public EventBusBuilder executorService(ExecutorService executorService) {
            this.executorService = executorService;
            return this;
        }
    
        //是否进行方法验证,例如验证其修饰符...
        public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
            if (skipMethodVerificationForClasses == null) {
                skipMethodVerificationForClasses = new ArrayList<>();
            }
            skipMethodVerificationForClasses.add(clazz);
            return this;
        }
    
      //是否忽略索引功能,文章顶上有说明(不使用时建议自定义为true,后面少验证)
      public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
            this.ignoreGeneratedIndex = ignoreGeneratedIndex;
            return this;
        }
    
      //......
    

    EventBus的Builder不是很复杂,可以自己在使用的时候自己查看方法。

    拓展——索引功能

    导库请看官方讲解:http://greenrobot.org/eventbus/documentation/subscriber-index/
    我这里贴出生成类关键代码:

        private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
    
        static {
            SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
    
            putIndex(new SimpleSubscriberInfo(Main2Activity.class, true, new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("onRecived", MessageEvent.class, ThreadMode.MAIN, 0, 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;
            }
        }
    

    分析:EventBus就是在编译时期找到被注解的类,并由此生成一个类。生成类内部维护着一个static Map,该Map的key是订阅者类,value是订阅者信息对象(包含订阅方法名等重要信息),因此在我们register()的时候,就会使用getSubscriberInfo()方法将value提出来放入EventBus中的订阅者集合中去,这样在post的时候,就可以通过EventBus中的集合找到我们对应的订阅方法,并去invoke()实现订阅方法,从而达到传递消息功能。

    对比:register()的时候:没有索引功能的情况下,只能通过反射找到订阅类中的注解,而使用反射寻找订阅方法是比较消耗时间的。而有索引功能的情况下,在项目编译时就已经生成了索引类,只需直接调用getSubscriberInfo()拿到订阅信息,而无需再使用反射去找注解等系列过程,时间上肯定是节约了不少。

    最后

    EventBus使用时业务代码是相当简洁的,其也实现了发送者和接收者之间很大程度的解耦,用户只需关心其事件类型即可,而且不像Intent必须使用可序列化的数据,并且EventBus支持粘性事件。
    我觉得EventBus也有些缺点,个人拙见:EventBus采用的模式是多对一的模式,由于规定发送、接收方的只有事件类型,这导致发送者不知道其接收者究竟有哪些,在接收者错误发生时,接收者也不知道该事件来自于哪个发送者。还有一个问题在于其粘性事件的处理,粘性事件在实际开发中是比较常见的,接收者只要已注册即会接收到粘性事件,一旦粘性事件堆积过多,或者说已经“过期”的粘性事件没能及时清除,这将导致不可预测的结果。

    笔者水平有限,有写得不好的地方,请大胆指出~
    转载请注明出处~

    相关文章

      网友评论

          本文标题:Android EventBus3.0+ 简单使用及源码讲解

          本文链接:https://www.haomeiwen.com/subject/buagmqtx.html