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

2019-03-21  本文已影响0人  ZuYuan

前言

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采用的模式是多对一的模式,由于规定发送、接收方的只有事件类型,这导致发送者不知道其接收者究竟有哪些,在接收者错误发生时,接收者也不知道该事件来自于哪个发送者。还有一个问题在于其粘性事件的处理,粘性事件在实际开发中是比较常见的,接收者只要已注册即会接收到粘性事件,一旦粘性事件堆积过多,或者说已经“过期”的粘性事件没能及时清除,这将导致不可预测的结果。

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

上一篇下一篇

猜你喜欢

热点阅读