EventBus源码解析(二)-EventBus实例化

一、默认EventBus实例

当某个类需要订阅事件时,我们通常会先在这个类的某处(如初始化方法)添加如下代码以完成EventBus的注册。

EventBus.getDefault().register(this);

然后在适当的地方添加如下代码以完成EventBus的注册解除,防止泄露。

EventBus.getDefault().unregister(this);

无论是注册或者解除注册,无一例外,都是通过EventBus.getDefault()获取到的EventBus实例来完成的。那么getDefault()方法做了什么操作呢?我们跟进代码看看。

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

getDefault()方法其实很简单,就是使用单例模式,获取到了唯一的EventBus对象。初次使用时,instance对象势必为null,此时就会通过EventBus的无参构造函数创建出一个EventBus实例对象。无参构造函数的实现如下:

    public EventBus() {
        this(DEFAULT_BUILDER);
    }

可以看到,EventBus的无参构造函数,最终调用的还是带有单个参数的构造函数,其参数类型是EventBusBuilder,望文生义,EventBusBuilder明显是采用建造者模式实现的,这部分我们后面再分析。无参构造函数调用了有参构造函数,并传入了DEFAULT_BUILDER实参。那么,DEFAULT_BUILDER又是怎样的呢?其实它就是一个EventBusBuilder对象,只有预设定的默认配置。

 private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

DEFAULT_BUILDER传入到有参的EventBus构造函数后,会执行一些初始化的工作,如下:

 EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        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;
        executorService = builder.executorService;
    }

在这里,会取出EventBusBuilder的各项配置值,赋值给相应的EventBus中的成员。这是很典型的建造者模式,一般当有多项配置时,我们会考虑采用这种模式。

经历以上流程之后,默认的EventBus实例就通过getDefault方法获取到了,并且这个EventBus是全局唯一的。

代码分析到这里,我们大概可以知道了,EventBus实例化可以有两种方式:

  • 使用默认的配置,即getDefault()方式
  • 使用EventBusBuilder自定义配置,之后通过build生成

二、默认配置

EventBusBuilder是EventBus的建造者,负责初始化EventBus的各项配置,并生成EventBus对象。通过第一节的分析,我们会有一个疑问:EventBus的默认配置到底是怎么样的呢?这个问题其实可以换个问法:EventBusBuilder的默认配置到底是怎么样的呢?让我们赶紧追进代码分析吧。

public class EventBusBuilder {
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    boolean logSubscriberExceptions = true;
    boolean logNoSubscriberMessages = true;
    boolean sendSubscriberExceptionEvent = true;
    boolean sendNoSubscriberEvent = true;
    boolean throwSubscriberException;
    boolean eventInheritance = true;
    boolean ignoreGeneratedIndex;
    boolean strictMethodVerification;
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    List<Class<?>> skipMethodVerificationForClasses;
    List<SubscriberInfoIndex> subscriberInfoIndexes;
    Logger logger;
    MainThreadSupport mainThreadSupport;

    ...
}

分析以上EventBusBuilder的成员及赋值,可以得出如下信息:

logSubscriberExceptions :是否打印订阅者异常信息,默认开启
logNoSubscriberMessages :某个事件没有订阅者时,是否打印信息,默认开启
sendSubscriberExceptionEvent :出现订阅者异常时,是否发送异常事件,默认开启
sendNoSubscriberEvent :某个事件没有订阅者时,是否发送无订阅者的事件,默认开启
throwSubscriberException :是否抛出订阅者异常信息,默认关闭
eventInheritance :事件是否可以继承形式订阅,默认开启
ignoreGeneratedIndex :忽略索引生成,默认关闭
strictMethodVerification :是否开启方法严格验证,默认关闭
executorService :线程池,默认是newCachedThreadPool,即没有核心线程、但最大线程数是Integer.MAX_VALUE的线程池
skipMethodVerificationForClasses :跳过为订阅者类里面的方法进行校验,校验包括注解信息、修饰符是否是public且非static\final的,默认为空
subscriberInfoIndexes :订阅者信息索引,由注解处理器生成
mainThreadSupport :专为Android的主线程定制,持有主线程looper引用

结合第一节的分析,到这里我们可以得出结论,EventBus的默认配置是:

  • 当出现订阅者异常时,打印异常log
  • 当事件没有订阅者时,打印没有订阅者log
  • 当出现订阅者异常时,发送异常事件
  • 当事件没有订阅者时,发送无订阅者事件
  • 捕获异常信息,防止崩溃
  • 事件可以继承
  • 编译时生成索引
  • 采用最大限制是Integer.MAX_VALUE的缓存线程池
  • 为每个订阅者类都进行方法校验
  • 当处于Android平台时,确保可以切换到主线程

三、自定义配置

那么如何进行自定义的EventBus配置呢?聪明的你一定想到了。是的,我们可以通过EventBusBuilder来实现自定义配置。EventBusBuilder内部提供了一系列的配置方法,方便用户采用链式调用的方式,来生成一个EventBusBuilder对象。然而,通过查看EventBus源码可知,EventBusBuilder的构造方法的修饰符是protected的,也就是说,用户无法通过直接new的方式来创建EventBusBuilder对象。这样不就无法自定义配置了吗?别急,虽然我们无法直接new出一个EventBusBuilder对象,但是EventBus类提供了一个静态方法builder(),该方法内部默认new了一个EventBusBuilder对象,如下:

    public static EventBusBuilder builder() {
        return new EventBusBuilder();
    }

通过builder()方法,用户就可以获取到EventBusBuilder对象,之后就可以随心所欲地自定义配置了。配置有如下两种使用姿势:

姿势1:
        EventBus.builder().logNoSubscriberMessages(false)
                .logSubscriberExceptions(false).eventInheritance(false)...
                .installDefaultEventBus();
姿势2:
              EventBus.builder().logNoSubscriberMessages(false)
                .logSubscriberExceptions(false).eventInheritance(false)...
                                .build();

上述两种姿势看起来非常相似,但其实是有区别的,区别就在于创建EventBus实例的方式,姿势1使用的是installDefaultEventBus方法,姿势2使用的是build方法。我们来看看这两种方法的内部实现。

    public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }
public EventBus build() {
        return new EventBus(this);
    }

看出区别了吗?installDefaultEventBus方法在内部其实也是调用的build方法,但不同的是,installDefaultEventBus可以确保EventBus实例的全局唯一性,当defaultInstance非空时,会直接抛出异常。而姿势2直接使用build方法创建EventBus实例的方式,则需要用户自身确保EventBus实例的全局唯一性。

EventBus并非一定要全局唯一,但确保全局唯一,不是可以更好更合理地管理和分发事件吗?


四、结束语

通过以上几小节的分析,我们已经知道了EventBus的默认配置情况,以及如何自定义配置。但如果再仔细观察,会发现,在 EventBus(EventBusBuilder builder) 构造函数中,还默认实例化了几个成员对象:

        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();

        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);

        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);

这些成员是用来做什么的呢?我们先留个悬念,在后面的几章中进行详细的分析。

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

推荐阅读更多精彩内容