Android 开源库-深入理解 EventBus 的创建

本文深入探讨EventBus的创建机制,揭示其为何并非传统单例,而是支持多实例,剖析建造者模式在配置中的作用,以及如何结合getDefault方法实现灵活的全局事件总线。通过实例演示,理解EventBus设计背后的逻辑和实用性。
摘要由CSDN通过智能技术生成

1. 前言

大约在 2016 年接触了 EventBus,在项目中也有用到过。但是,说到 EventBus 的原理,我只能这样了:

在这里插入图片描述

如果一点一点地去学习,大概可以理解 EventBus 的原理。理解了原理之后,对于自己的开发技能提升也是有帮助的。

本文不涉及 EventBus 如何接入,如何使用的介绍,这些请参考 EventBus 开源库的地址

本文主要介绍 EventBus 的创建。

主要会说明以下几个问题:

  1. EventBus 的创建真的是单例吗?
  2. EventBus 使用建造者模式的好处是什么?
  3. EventBus 是如何建造者模式和 getDefault 方法结合起来的?

2. 正文

2.1 EventBus 的创建真的是单例吗?

public class EventBus {
	static volatile EventBus defaultInstance;
    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;
    }
    public EventBus() {
        this(DEFAULT_BUILDER);
    }
}

使用关键字 staticvotile 修饰了一个成员变量;getDefault() 方法里面通过双重校验锁来获取实例,这不就是经典的双重校验锁的单例模式吗?

但是,我们看 EventBus 的空参构造方法,它是 public 的,也就是说,外部通过这个构造方法就可以创建很多实例,这一点就不符合单例模式。

另外,我们从 getDefault() 这个方法名,可以看到,是为了获取一个默认的实例而已。

思考一下,EventBus 的空参构造方法为什么要是 public 的?

既然 EventBus 有公开的构造方法,那么说明 EventBus 的设计者允许调用者创建多个 EventBus 的实例,也就是说,EventBus 的设计者允许开发者同时拥有多个事件总线。

但是,有的开发者可能会问:这有什么好处呢?为什么不是 private 修饰从而形成一个经典的单例模式呢?

我们从 public EventBus() 方法的注释来看:

Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a central bus, consider {@link #getDefault()}.
创建一个新的EventBus实例;每个实例都是一个单独的作用域,在其中传递事件。若要使用中央总线,请考虑{@link #getDefault()}。

这里的重点是:每个实例都是一个单独的,隔离的作用域,在其中传递事件。也就是说,一个 EventBus 实例发送的数据只能由注册了这个 EventBus 的订阅者接收,而不会由注册了别的 EventBus 的订阅者接收。

这里通过举例子的方式来说明多事件总线的场景:有一个事件发布者Publisher,有两个事件订阅者 Subscriber1、Subscriber2,它们都会接收来自事件发布者 Publisher 的 Event 消息。但是,Subscriber1 只希望接收到特定的Event消息,比如 value 字段大于 1 的消息;Subscriber2 也只希望接收到特定的 Event 消息,比如 value 字段小于 1 的消息。这时,如果使用一个 EventBus 实例,会是什么情况呢?绘图如下:

在这里插入图片描述

这里的 EventBus 实例,使用 EventBus.getDefault() 方法来创建就可以了。

这样不符合要求,Subscriber1 接收到了不需要的 value<1的 Event,同样,Subscriber2 接收到了不需要的 value>1 的 Event,这无疑是一种浪费。

这种情况下,可以使用两个 EventBus 实例,Subscriber1 和 Subscriber2 分别注册不同的事件总线中,在发布不同的事件消息时,就使用不同的事件总线来发送。这样,每个订阅者都只会接收到自己需要的事件消息了,不存在浪费行为。绘图如下:

在这里插入图片描述

需要说明的是,这里的 EventBus1 和 EventBus2 都通过EventBus.getDefault() 方法来创建是不可以的,因为多次调用EventBus.getDefault() 方法返回的是同一个 EventBus 实例。

这里想说一下 getDefault() 方法的定位:当应用内只使用了一个事件总线时,那么使用 getDefault() 方法就可以了;当应用内需要使用多个事件总线时,那么只有一个事件总线可以使用 getDefault() 方法来获取,其余的事件总线需要参考 getDefault() 方法另行创建方法来获取。

开源库里的默认实现其实有两层含义:

  1. 方便开发者使用:开发者如果没有额外的需求,直接使用这个默认实现就可以了,非常方便;
  2. 给开发者打个样:开发者如果有额外的需求,就可以参考默认实现,写出符合自己需求的实现。

参考 EventBusgetDefault 方法,EventBus1EventBus2 的代码实现如下:

public class EventBus1 {

    static volatile EventBus defaultInstance;

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

    static volatile EventBus defaultInstance;

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

在发送事件的地方,代码如下:

var value = 0
binding.btnPost.setOnClickListener {
    Log.d(TAG, "post: value=$value")
    if (value == 2) {
        EventBus1.getDefault().post(Event(value))
        value = 0
    } else {
        EventBus2.getDefault().post(Event(value))
        value = 2
    }
}

注册,接收的代码如下:

class SecondFragment : Fragment() {
    ...
    override fun onStart() {
        super.onStart()
        EventBus1.getDefault().register(this)
    }

    override fun onStop() {
        super.onStop()
        EventBus1.getDefault().unregister(this)
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onEvent(event: Event) {
        Log.d(TAG, "onEvent: value=${event.value}")
    }

    companion object {
        private const val TAG = "SecondFragment"
    }
}
class ThirdFragment : Fragment() {
    ...
    override fun onStart() {
        super.onStart()
        EventBus2.getDefault().register(this)
    }

    override fun onStop() {
        super.onStop()
        EventBus2.getDefault().unregister(this)
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onEvent(event: Event) {
        Log.d(TAG, "onEvent: value=${event.value}")
    }

    companion object {
        private const val TAG = "ThirdFragment"
    }
}

打印日志如下:

D/FirstFragment: post: value=0
D/ThirdFragment: onEvent: value=0
D/FirstFragment: post: value=2
D/SecondFragment: onEvent: value=2

小结一下:

  1. EventBus 并没有使用经典的双重校验锁单例模式,因为它的空参构造方法是 public 的;
  2. 通过 EventBus 的 的 getDefault() 方法可以获取一个全局的事件总线,这是 EventBus 库设计者提供给开发者的一个默认实现而已。

2.2 EventBus 使用建造者模式的好处是什么?

看一下 EventBus 的空参构造

public EventBus() {
    this(DEFAULT_BUILDER);
}

内部调用了带一个参数的EventBus(EventBusBuilder builder) 构造方法,传入的是 DEFAULT_BUILDER

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

看一下 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() {
    }

    public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
        this.logSubscriberExceptions = logSubscriberExceptions;
        return this;
    }

    public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
        this.logNoSubscriberMessages = logNoSubscriberMessages;
        return this;
    }

    public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {
        this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent;
        return this;
    }

    public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {
        this.sendNoSubscriberEvent = sendNoSubscriberEvent;
        return this;
    }

    public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) {
        this.throwSubscriberException = throwSubscriberException;
        return this;
    }

    public EventBusBuilder eventInheritance(boolean eventInheritance) {
        this.eventInheritance = eventInheritance;
        return this;
    }

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

    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
        return this;
    }

    public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {
        this.strictMethodVerification = strictMethodVerification;
        return this;
    }

    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

    public EventBusBuilder logger(Logger logger) {
        this.logger = logger;
        return this;
    }

    Logger getLogger() {
        if (logger != null) {
            return logger;
        } else {
            return Logger.Default.get();
        }
    }

    MainThreadSupport getMainThreadSupport() {
        if (mainThreadSupport != null) {
            return mainThreadSupport;
        } else if (AndroidComponents.areAvailable()) {
            return AndroidComponents.get().defaultMainThreadSupport;
        } else {
            return null;
        }
    }

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

}

可以看到,EventBusBuilder 是使用了建造者模式。对于 EventBus 来说,使用建造者模式是非常有用处的。

为什么这样说呢?

我们看通过 EventBusBuilder 来配置的 EventBus 需要的参数竟然有 13 个,这已经非常多了。

如果没有 EventBusBuilder 来辅助进行配置的话,这些参数就都需要 EventBus 自己写函数进行配置了。这样当开发者使用 EventBus 时,就会有很多配置参数的函数暴露给开发者,而开发者不得不仔细进行选择,这样无形之中增加了开发者的使用成本。

那么,使用了 EventBusBuilder 就会解决这个问题吗?

是的,当使用了 EventBusBuilder 后,EventBus 就不会有 13 个配置参数的函数暴露给开发者了,而是要求开发者创建一个 EventBusBuilder 对象,通过调用这个构建器的配置参数的函数进行参数配置;在内部,构建器会把这些配置参数一一赋值给 EventBus

到这里,可能有的开发者会说,这不过是把 EventBus 的参数配置函数转移给了 EventBusBuilder 构建器而已?

目前看来,确实如此。但是,仅仅这样的转移,就可以极大地减少开发者的使用成本。当开发者使用 EventBus 时,设计者默认提供一套默认的配置(即 DEFAULT_BUILDER),开发者就先不必研究每一个配置参数函数的作用,快速地在代码中看到这个库的功能。当随着开发过程的进行,开发者发现默认的配置不能满足需求时,再去查看构建器提供的参数配置函数,并进行调整,满足需求,这样也符合循序渐进了解学习一个东西的过程。

另外,不要忘记了 13 个参数有不少配置参数是有默认值的,也就是说,构建器的 13 个配置函数并不是每次都要调用的。

小节一下:

EventBus 有很多配置参数,而不少参数都具有默认值,为了减少开发者的使用成本,使用了建造者模式。

这里再说明一下,建造者模式的使用场景有多个,这里只是用到了其中一个,其他的场景这里不做介绍了。

2.3 EventBus 是如何建造者模式和 getDefault 方法结合起来的?

前面两节我们讨论了 EventBus 全局事件总线的创建,EventBus 使用建造者模式灵活地进行参数配置。

我们知道,创建全局事件总线时,我们实际上只是使用默认的参数配置,那么如何把建造者模式的灵活参数配置和创建全局事件总线结合起来呢?也就是说,怎样才能在创建全局事件总线时得到灵活的参数配置呢?

这个好像不难,修改我们的 EventBus1 为:

public class EventBus1 {

    static volatile EventBus defaultInstance;

    public static EventBus getDefault() {
        EventBus instance = defaultInstance;
        if (instance == null) {
            synchronized (EventBus.class) {
                instance = defaultInstance;
                if (instance == null) {
                    instance = defaultInstance =
                        	// 使用建造者模式来创建 EventBus
                            EventBus.builder()
                                    .ignoreGeneratedIndex(false)
                                    .logSubscriberExceptions(true)
                                    .strictMethodVerification(true)
                                    .build();
                }
            }
        }
        return instance;
    }
}

从功能实现上来说,这样写是没有问题的;但是,从代码结构上来说,EventBus1 类的职责是获取一个全局的事件总线,现在增加进行参数配置的功能,这违反了单一职责原则。

或许有的开发者会说,这有点吹毛求疵了吧?这样改不是很方便吗?难道你还有别的高招吗?

好吧。

我们先想一下,EventBus 类的源码有一个 getDefault() 方法来获取全局事件总线,然后 EventBus 也是可以通过建造器灵活配置参数的,但是 EventBus 类并没有在 getDefault() 方法里面直接使用建造器来构建。

那么,EventBus 是怎么做的呢?

我们查看 EventBusBuilder 类,会发现有这么一个函数:

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

它是什么作用呢?查看一下方法注释:

Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders’ values. Must be done only once before the first usage of the default EventBus.

使用此建造器的值安装{@link EventBus#getDefault()}返回的默认事件总线。只能在首次使用默认EventBus之前执行一次。

我们可以在应用初始化时调用此方法:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        val eventBus = EventBus.builder()
            .ignoreGeneratedIndex(false)
            .logSubscriberExceptions(true)
            .strictMethodVerification(true)
            .installDefaultEventBus()
        Log.d(TAG, "onCreate: eventBus=${eventBus.hashCode()}")
        Log.d(TAG, "onCreate: EventBus.getDefault()=${EventBus.getDefault().hashCode()}")
    }

    companion object {
        private const val TAG = "App"
    }
}

打印信息:

D/App: onCreate: eventBus=37228617
D/App: onCreate: EventBus.getDefault()=37228617

可以看到,调用 EventBus.getDefault() 时,就会获取到这里提前构建好的 EventBus 实例了。

需要注意的是,

  • installDefaultEventBus() 方法的调用一定要在 EventBus.getDefault()之前,否则会抛出异常;
  • installDefaultEventBus()方法内部使用了同步代码块,来保证对 EventBus.defaultInstance 的读取和赋值操作的线程安全。

回到自己创建的事件总线 EventBus1 类,该如何实现构建器灵活参数配置和获取全局事件总线结合在一起?

使用 Kotlin 中的扩展函数,对 EventBusBuilder 添加扩展函数,代码如下:

fun EventBusBuilder.installEventBus1(): EventBus {
    synchronized(EventBus::class.java) {
        if (EventBus1.defaultInstance != null) {
            throw EventBusException(
                "EventBus1 instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior."
            )
        }
        EventBus1.defaultInstance = build()
        return EventBus1.defaultInstance
    }
}

使用代码如下:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        val eventBus = EventBus.builder()
            .ignoreGeneratedIndex(false)
            .logSubscriberExceptions(true)
            .strictMethodVerification(true)
            .installEventBus1()
        Log.d(TAG, "onCreate: eventBus=${eventBus.hashCode()}")
        Log.d(TAG, "onCreate: EventBus1.getDefault()=${EventBus1.getDefault().hashCode()}")
    }

    companion object {
        private const val TAG = "App"
    }
}

打印日志如下:

D/App: onCreate: eventBus=37228617
D/App: onCreate: EventBus1.getDefault()=37228617

可以看到,调用 EventBus1.getDefault() 时,就会获取到这里提前构建好的 EventBus 实例了。

3. 最后

本文通过问题的形式讨论了 EventBus 如何创建多条事件总线,EventBus 为什么要使用建造者模式,以及如何把建造者模式和全局事件总线相结合;并且,提供了代码实现,本文相关代码已上传到 github

希望可以帮助到大家。

参考

  1. 《Android 源码设计模式解析与实战》第 3 章 自由扩展你的项目—Builder模式;

    介绍了使用建造者模式的好处。

  2. EventBus 3.0+ 源码详解(史上最详细图文讲解)(上);

    这篇文章说明了 EventBus 允许多条事件总线的设计思想。

  3. 深扒 EventBus:getDefault

    这篇文章说明了建造者模式和全局事件总线获取的结合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值