手把手debug源码之EventBus

本文基于EventBus3.1.1进行源码分析,以发送一个正常事件和粘性事件为例,探索EventBus工作的整个过程。你也可以直接下载demo同步运行调试,Gif示例如下:

源码分析EventBus展示.gif

gif中首先展示了发送一个LoginSuccessEvent的正常事件,在MainActivity和SecondActivity中都有订阅,这里主要展示一对多的场景;接着分别以正常方式和粘性方式发送了一个RegisterSuccessEvent事件,看看在GoToLoginActivity中有怎样不同的表现。

本文的思路是先分析注册和注销的流程,也就是订阅和解订阅;再分析发布正常事件和发布粘性事件的流程。

1. 注册和注销的流程分析

EventBus.getDefault().register(this)
EventBus.getDefault().unregister(this)

1.1. register

首先通过EventBus.getDefault()拿到实例对象,源码如下所示,

public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

这是一种双重校验的懒汉式单例,双重校验机制只会在第一次创建实例时有锁的介入,一旦实例创建成功,下次再获取实例就不会进入锁块了。还有一点需要注意务必要用volatile关键字修饰defaultInstance变量,保证在操作defaultInstance对象时,都是从内存中加载最新的状态。后续通过getDefault()拿到的都是defaultInstance这个实例对象了。接着看看regitster()干了什么,源码如下所示:

public void register(Object subscriber) {
    //拿到订阅者的Class实例对象
    Class subscriberClass = subscriber.getClass();
    //找到该订阅者的所有订阅方法
    List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //发起订阅,保存订阅者与订阅方法的关系
            subscribe(subscriber, subscriberMethod);
        }
    }
}

首先会拿到订阅者的Class实例对象,然后通过subscriberMethodFinder去查找该类所有的订阅方法,subscriberMethodFinder这个实例对象从命名就能看出是订阅方法查找器;最后利用synchronized保证线程安全便利发起订阅,保存订阅者与订阅方法的之间的关系。下图是debug register()的截图标注说明:

register.jpg

接下来我们详细分析findSubscriberMethods(subscriberClass)和subscribe(subscriber, subscriberMethod)这两个方法。

1.1.1. findSubscriberMethods(subscriberClass)

List findSubscriberMethods(Class subscriberClass) {
    //从缓存中拿取订阅者的所有订阅方法
    List subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    //是否忽略设置的索引,索引目的是把在注册时需要遍历订阅者所有方法的行为,提前到在编译时完成,
    //在编译时apt插件通过EventBusAnnotationProcessor分析注解,并利用注解标识的相关类的信息去生成相关的类
    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;
    }
}

进入findSubscriberMethods方法,首先会去缓存中找,如找到则直接return subscriberMethods,否则接着往下看看是否忽略设置的索引,默认false,此时会进入到findUsingInfo通过索引查找,最后判断subscriberMethods是不是空,不空的话则将订阅者和订阅方法放入缓存中。整个逻辑非常简单:先在缓存中找订阅方法集合列表,找不到再通过反射或者索引找,最后还是没找到,则抛出EventBusException,否则将订阅者和订阅方法放入缓存供下次使用。接下来我们看下findUsingInfo的源码,如下所示:

private List 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) {
                //checkAdd是为了避免在父类中找到的方法是被子类重写的,此时应该保证回调时执行子类的方法
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //则通过反射的方式查找订阅方法信息
            findUsingReflectionInSingleClass(findState);
        }
        //findState移动到该类的父类中,准备下次便利父类的订阅方法信息
        findState.moveToSuperclass();
    }
    //释放findState并返回订阅者方法
    return getMethodsAndRelease(findState);
}

进入findUsingInfo方法,通过FindState存放临时变量,然后遍历子类及其父类的订阅方法集合列表,最后返回该集合列表。由于demo中没有采用索引预生成订阅方法信息,虽然上面的ignoreGeneratedIndex进入了索引查找分支,但最终还是会通过findUsingReflectionInSingleClass方法反射查找该类的所有订阅方法。下面是findUsingReflectionInSingleClass的源码:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        //拿到所有声明的方法
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        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注解
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class eventType = parameterTypes[0];
                    //两层检查:1检查事件类型 2检查方法的完整签名
                    if (findState.checkAdd(method, eventType)) {
                        //拿到线程模型
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        //创建SubscriberMethod,并放到findState.subscriberMethods中
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

进入findUsingReflectionInSingleClass方法,主要逻辑是通过反射拿到findState.clazz的所有声明方法methods,然后遍历methods,判断method的入参长度是否为1,判断method的注解是否为Subscribe,判断method的事件类型以及完整签名,最后创建SubscriberMethod放到findState中。

至此查找订阅方法的流程完毕,不关注过多细节的话,流程就是:findSubscriberMethods方法代码段中的注释信息,即先从缓存中拿取订阅者的所有订阅方法,如没找到则通过反射或者索引方式继续找,找到了则更新缓存并返回,否则抛出异常。

1.1.2. subscribe(subscriber, subscriberMethod)

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //拿到事件类型,就是你自己定义的Event,比如LoginSuccessEvent
    Class eventType = subscriberMethod.eventType;
    //用订阅者和订阅方法构造Subscription,即Subscription描述了订阅者和订阅方法之间的关系
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //从subscriptionsByEventType这个Map中获取对应事件类型的subscriptions集合
    CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        //如未获取到,则创建一个新的
        subscriptions = new CopyOnWriteArrayList<>();
        //用eventType为key,subscriptions为value存放到Map集合subscriptionsByEventType中
        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> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        //用subscriber为key,subscribedEvents为value存放到Map集合typesBySubscriber中
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    //判断是否为粘性事件
    if (subscriberMethod.sticky) {
        //事件是否继承标识
        if (eventInheritance) {
            Set, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry, Object> entry : entries) {
                Class candidateEventType = entry.getKey();
                
                //有两个Class类型的类象,一个是调用isAssignableFrom方法的类对象(后称对象a),以及方法中作为参数的这个类对象(称之为对象b),这两个对象如果满足以下条件则返回true,否则返回false:
                //a对象所对应类信息是b对象所对应的类信息的父类或者是父接口,简单理解即a是b的父类或接口
                //a对象所对应类信息与b对象所对应的类信息相同,简单理解即a和b为同一个类或同一个接口

                //判断eventType是否与candidateEventType拥有一致的类型信息或者为继承关系
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            //拿到粘性事件
            Object stickyEvent = stickyEvents.get(eventType);
            //执行粘性事件
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

拿到订阅方法后,用Subscription描述订阅者和订阅方法之间的关系,并构造出一个实例对象newSubscription,然后在subscriptionsByEventType这个Map集合中查找eventType对应的subscriptions集合。subscriptionsByEventType这个Map以eventType为key,subscriptions为value,它保存了事件类型与subscriptions集合之间的关系,通过查找这个集合,可以知道某个事件对应的所有订阅者以及订阅方法。然后对比newSubscription的优先级,再插入到subscriptions,这里可以保证优先级高的订阅方法先被执行。接着维护一个typesBySubscriber的Map集合,这个集合主要是用于判断某个订阅者是否已被注册,避免重复注册。最后判断该订阅方法是否为粘性方法,如果是的话,则直接发送粘性事件给到订阅者,而不必再等待外部调用post。

下面是debug跳到SecondActivity时,发起注册的图示,可以看到SecondActivity中定于了LoginSuccessEvent事件,订阅方法是processLoginSuccessForNotify;而在MainActivity中也订阅了LoginSuccessEvent事件,订阅方法是processLoginSuccess,所以它们一同被维护到了subscriptionsByEventType集合中。

SecondActivity-register.png

总结:注册事件时,先获取到EventBus的实例对象,然后调用register方法,先找到该订阅者里面的所有订阅方法(findSubscriberMethods),然后绑定事件类型、订阅者与订阅方法之间的关系,即维护好Map集合subscriptionsByEventType。

1.2. unregister

public synchronized void unregister(Object subscriber) {
    //根据订阅者拿到所有的事件
    List> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //解绑订阅者的所有订阅事件
        for (Class eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        //移走该订阅者
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

注销的流程相对简单,通过unsubscribeByEventType方法维护下Map集合subscriptionsByEventType,遍历移除该订阅者下的订阅事件。下面是unsubscribeByEventType的源码:

private void unsubscribeByEventType(Object subscriber, Class eventType) {
    //拿到该事件的subscriptions集合
    List subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            //判断该subscription对应的订阅者是不是跟要注销的订阅者一致
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                //移除该subscription
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

2. 发布事件

发布事件时,并不需要你先去调用register方法注册,这点在新手中很容易出错。发布事件时,也需要先拿到EventBus实例,然后再调用post或者postSticky方法。

2.1. post

//post方法源码
public void post(Object event) {
    //拿到PostingThreadState实例对象,用于存放发布时的一些变量
    PostingThreadState postingState = currentPostingThreadState.get();
    //拿到事件队列
    List 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;
        }
    }
}

//postSingleEvent源码
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class eventClass = event.getClass();
    boolean subscriptionFound = false;
    //事件是否有继承关系,Java中都是继承自Object,所以这里通常是true
    if (eventInheritance) {
        //找到eventClass所有的事件,包括其所有父类
        List> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        //遍历所有事件
        for (int h = 0; h < countTypes; h++) {
            Class clazz = eventTypes.get(h);
            //发送事件
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } 
    ...
}

进入post方法,主要就是将事件类型加入到事件队列中,然后遍历队列,调用postSingleEvent逐个发布事件。而在postSingleEvent中,会拿到事件的所有父类存放到eventTypes中,再遍历eventTypes逐个调用postSingleEventForEventType。这里的流程都非常简单,接着看源码:

//postSingleEventForEventType源码
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) {
    CopyOnWriteArrayList subscriptions;
    synchronized (this) {
        //从Map集合subscriptionsByEventType中查找事件对应的subscriptions
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        //遍历subscriptions
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                //逐个发送事件
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

//postToSubscription源码
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    //根据线程模型进入不同的分支
    switch (subscription.subscriberMethod.threadMode) {
        ...

        case MAIN:
            if (isMainThread) {
                //反射调用订阅者中的方法
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        
        ...
    }
}

postSingleEventForEventType中最主要的事情就是从subscriptionsByEventType这个Map集合中取出对应事件的subscriptions集合,然后遍历它再逐个发送事件。进入到postToSubscription中后,根据subscriberMethod中的threadMode进入不同的分支,再反射调用订阅者中对应事件的对应订阅方法。invokeSubscriber中的关键代码如下:

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

当进入到SecondActivity页面,点击按钮时,会依次反射MainActivity中的processLoginSuccess,再反射SecondActivity中的processLoginSuccessForNotify,下面是debug按钮点击是invokeSubscriber的图示说明:

invokeSubscriber.jpg
invokeSubscriber-2.jpg

总结:post这一过程相比register的过程还要简单,主要就是从subscriptionsByEventType这个Map集合中查找事件的subscriptions集合,然后再反射执行订阅方法。是的,整个过程就是如此简单。

2.2. postSticky

public void postSticky(Object event) {
    //保存event到Map集合stickyEvents中
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    //发布事件
    post(event);
}

postSticky与post的区别在于postSticky中将事件保存到了Map集合stickyEvents中,然后再发布事件,仅此而已,就是这么“粗暴直接”。这里有两处入口会执行粘性事件,第一处是postSticky,这里会反射执行该粘性事件的所有订阅方法,第二处其实上面分析register时就提到过了,在注册订阅者时就会判断该订阅者下的订阅方法是否为粘性,是的话就会立即执行订阅方法。

3. 一点感想

EventBus的原理其实是利用观察者模式,维护了一套观察者(订阅者)与被观察者(发布者所发布的事件)之间的Map集合。当发起订阅时(即register),就会以订阅者以及订阅方法为value,事件为key维护到Map中。当发布事件时,就会在Map集合中查找该事件对应的订阅者以及订阅方法,找到了就反射调用订阅方法,此时有可能某些订阅者是没有被创建或者被销毁了的,那么此时反射失败,EventBus框架就会内部消化掉这个异常。下面这张图我在官网图示的基础上增加了自己的一些理解和注释:

EventBus-Publish-Subscribe.png

上面我只是分析了在主线程中发布,主线程中消费事件的流程,EventBus还有几个线程模型可供选择,分别是:POSTING、MAIN、MAIN_ORDERED、BACKGROUND、ASYNC,这里我不再一一赘述,有兴趣的可以自行debug调试。

当我们在维护自己的基础库供他人使用的时候,如果涉及到要实时通知功能的,可以借鉴EventBus的设计思路,EventBus的使用时非常简洁的,对外暴露的接口也异常简单,这对于设计框架供别人使用时很重要的一个技能,如果提供的东西使用起来很复杂,务必会增加他们的学习成本,从而导致基础库难以推广。还可以借鉴EventBus的单例模式控制好自己框架的实例数量,以及用FindState类这种保存临时变量这种思想来优雅的书写代码。

再有一个就是反射是很重要的一个技能,作为框架开发以及基础库开发,很有可能会要用到,所以掌握好反射是必须的。

你可能感兴趣的:(手把手debug源码之EventBus)