EventBus3.0源码分析(一)register

项目中用到了很多优秀的框架,但是一直停留在会用的阶段是,没有硬着头皮去看看它们是如何实现的,趁着活终于不多了,打算把项目中的用到的框架都研究研究,学习一下优秀的代码。今天就看看EventBus吧,优秀的框架一般使用起来都特别简单,EventBus也不例外。EventBus的具体使用这里就不介绍了,无外乎register注册;unregister注销;@subscriber在合适的线程关心相应的事件;post/postSticky发送事件,我们就从EventBusregister开始吧。

大多数情况下我们都是通过EventBus的静态方法getDefault获得默认的EventBus对象,然后register注册。

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

很常见的单例设计模式,继续看看EventBus初始化做了什么

public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    // subscriptionsByEventType是一个map集合,存储着每个事件对应的subscription集合 (key:EventType value: List<subscription>)
    // subscription是对订阅者还有订阅方法SubscriberMethod的包装
    // SubscriberMethod又是对method,优先级,线程类型,是否接受粘性事件的包装
    subscriptionsByEventType = new HashMap<>();
    // typesBySubscriber也是一个map集合,存储着每个订阅者订阅的事件集合( key:订阅者 value:List<EventType>)        
    typesBySubscriber = new HashMap<>();
    // 粘性事件集合 
    stickyEvents = new ConcurrentHashMap<>(); 
    // 需要在主线程处理的事件的poster
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    // 需要Background线程处理的事件的poster
    backgroundPoster = new BackgroundPoster(this);
     // 需要异步处理的事件的poster
    asyncPoster = new AsyncPoster(this);
    // 索引个数
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    // 通过subscriberMethodFinder寻找订阅者的订阅方法 可以通过反射和索引两种方式
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    // 以下都是是否打印或者抛出异常的标志位
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    // 是否支持事件继承
    eventInheritance = builder.eventInheritance;
    // 异步和 BackGround 处理方式的线程池
    executorService = builder.executorService;
}

又是很常见的Builder设计模式对EventBus进行初始化。

接下来就是register

public void register(Object subscriber) {
    // 订阅者的类型
    Class<?> subscriberClass = subscriber.getClass();
    // 通过SubscriberMethodFinder类寻找该subscriber所有带@subscriber注解方法相关信息(SubscriberMethod)的集合。
    // SubscriberMethod实际上是对带@subscriber注解的方法一些信息的封装。
    // 包括该方法的Method对象,事件回调的线程ThreadMode,优先级priority,是否响应粘性事件sticky,以及最重要的所关心的事件类型eventType。
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

先看看上面findSubscriberMethods方法是如何获取到SubscriberMethod集合的:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 先看看缓存中有没有
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    // 是否忽略index,默认是false
    if (ignoreGeneratedIndex) {
        //通过反射查找
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //尝试从索引中查找
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    // 某些类调用了register方法,却没有带@Subscribe注解的方法,就会抛出如下异常(是不是很眼熟?)
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        // 放入缓存并且返回subscriberMethods
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

从上面的代码中可以看到有两种方式来获取订阅者中带@Subscribe注解的方法,这里其实是EventBus3.0的优化,之前都是通过反射,而新版如果启用了索引的话,会在编译时期,将所有订阅者及其带@Subscribe注解的方法相关信息通过硬编码的方式存放到map中,这样的话就可以直接通过key来获取了,下面是编译时期生成的索引类的部分代码:

 //存放所有的订阅者及其带`@Subscribe`注解的方法相关信息
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

static {
    SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
    // 将所有的订阅者及其关心的事件存放到map中
    putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("onEventHanlder1", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onEventHanlder2", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onEventHanlder3", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onEventHanlderInteger", Integer.class, ThreadMode.MAIN),
    }));

    putIndex(new SimpleSubscriberInfo(SecondActivity.class, true, new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("onSecondEvent1", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent2", String.class, ThreadMode.MAIN, 0, true),
        new SubscriberMethodInfo("onSecondEvent3", Integer.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent4", Integer.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent5", Integer.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent6", Integer.class, ThreadMode.MAIN),
    }));

}

private static void putIndex(SubscriberInfo info) {
    SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}

先来看看通过反射的方式获取List<SubscriberMethod>集合

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    // 从对象池中取一个FindState 
    FindState findState = prepareFindState();
    // 初始化FindState
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        // 通过反射寻找订阅方法相关信息
        findUsingReflectionInSingleClass(findState);
        // 有父类的话 findState.clazz 设置为父类
        // 没有的话findState.clazz = null, 结束寻找
        findState.moveToSuperclass();
    }
    // 释放资源并且方法findState中的subscriberMethods
    return getMethodsAndRelease(findState);
}

//通过反射查找
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        // 之所以比getMethods快,是因为getDeclaredMethods不包括继承的方法
        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();
        // 判断权限修饰符是否是public,是否是抽象方法,是否静态方法
        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();
                        // 将该方法相关信息(method,eventType,threadMode,priority,sticky)包装下添加到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)) {
            // 抛异常,带注解的方法必须是public,非静态,非抽象(是不是很眼熟,有时候手快少写public或者写成了private)
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

再来看看通过索引的方式吧

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        // 取出自动生成的索引中的subscriberInfo 
        findState.subscriberInfo = getSubscriberInfo(findState);
        // 如果仅仅启用了索引,但未将索引添加到eventbus中,还是采用反射的方式
        if (findState.subscriberInfo != null) {
            // subscriberInfo中包含除了method以外的subscriberMethods所需要的字段
            // 所以通过subscriberInfo的getSubscriberMethods方法进而通过createSubscriberMethod的方法获取method对象
            // 并且创建SubscriberMethod对象
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 反射的方式
            findUsingReflectionInSingleClass(findState);
        }
        // 有父类的话 findState.clazz 设置为父类
        // 没有的话findState.clazz = null, 结束寻找
        findState.moveToSuperclass();
    }
     // 释放资源并且方法findState中的subscriberMethods
    return getMethodsAndRelease(findState);
}

通过上述的两种方式找出该订阅者所有的订阅方法信息, 我们继续回到egister方法中,走完剩下的逻辑

synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // 事件类型
    Class<?> eventType = subscriberMethod.eventType;
    // 用Subscription类包装订阅者以及订阅方法
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // 取出所有订阅了该事件的Subscription集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    // 可能此eventType是第一次出现,初始化一下
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
         // 该订阅者register了两次会出现此异常
         // 可以自行查看Subscription equals方法 细节还是挺多的
        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);
    
    // 如果当前订阅方法接受粘性事件,并且订阅方法关心的事件在粘性事件集合中,那么将该event事件post给subscriber
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

register的流程大致分析完了,概况下就是通过合适的方式找到订阅者中所有的订阅方法及其信息、关心的事件,然后存到对应的集合中,如果存在关心粘性事件的订阅方法,那么在判断是否有之前post过的sticky事件,有则立即post给该订阅者。最后来张流程图装装逼吧:

register流程图

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 简介 我们知道,Android应用主要是由4大组件构成。当我们进行组件间通讯时,由于位于不同的组件,通信方式相对麻...
    Whyn阅读 529评论 0 1
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,464评论 0 50
  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 24,892评论 23 184
  • EventBus用法及源码解析目录介绍1.EventBus简介1.1 EventBus的三要素1.2 EventB...
    杨充211阅读 1,882评论 0 4
  • 最近接二连三的事,感觉自己受骗被利用,结果原来是这样… 事件1 妈妈抱怨诉苦,我以为妈妈需要帮助,结果,我出钱出力...
    天鹭阅读 792评论 0 0