ElegantBus 是一款 Android 平台,基于LivaData的消息总线框架,这是一款非常 优雅 的消息总线框架。
如果对 ElegantBus 的实现过程,以及考虑点感兴趣的可以看看前几节自吹
如果只是想先使用的,可以跳过,直接到跳到使用说明
消息总线 | 去除反射 | 不入侵系统包名 | 进程内Sticky | 跨进程Sticky | 跨APP Sticky | 事件可配置化 | 线程分发 | 消息分组 | 跨App安全考虑 | 常驻事件Sticky |
---|---|---|---|---|---|---|---|---|---|---|
LiveEventBus | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
ElegantBus | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ElegantBus 支持跨进程,且支持跨应用的多进程,甚至是支持跨进程间的粘性事件,支持事件管理,支持事件分组,支持自定义事件,支持同名事件等。
之所以称之为最优雅的总线,是因为她不仅实现了该有的功能,而且尽量选用最合适,最轻量,最安全的方式去实现所有的细节。 更值得夸赞的是使用方式的优雅!
随着LifeCycle的越来越成熟,基于LifeCycle的LiveData也随之兴起,业内基于LiveData实现的EventBus 也如雨后春笋一般拔地而起。
出于对技术的追求,看过了无数大牛们的实现,各位大神们思路也是出奇的神通,最基础的 LiveData 版 EventBus 其实大同小异,一个单例类管理所有的事件LivaData集合。如果不清楚的可以随便网上找找
反正基本功能 LivaData 都支持了,实现 EventBus 只需要把所有事件管理起来就完事了。
业内基于LiveData实现的EventBus,其实考虑的无非就是下面提到的五个挑战,有的人考虑的少,有的人考虑的多,于是各种方案都有。
ElegantBus 主要是集合各家之优势,进行全方面的考虑而产生的。
- 背景 LivaData的设计之初是为了数据的获取,因此无论是观察开始之前产生的数据,还是观察开始之后产生的数据,都是用户需要的数据,只要是有数据,当LifeCycle处于激活状态,数据就会传递给观察者。这个我们称之为 粘性数据。 这种设计对于事件来说有时候就不那么友好了,之前的事件用户可能并不关心,只希望收到注册之后发生的事件。
- 背景 同样是因为使用场景的原因,LivaData设计在跨线程时,使用post提交数据,只会保留最后一次数据提交的值,因为作为数据来说,用户只需要关心现在有的数据是什么。
-
背景 有时候我们应用需要设置多进程,不同模块可能允许在不同进程中,因为单例模式每个进程都有一份实体,所有无法达到跨进程,这时候设计IP方案选择。
-
说明 这里提一下为什么不选用广播方式,对广播有一定了解的都知道,全局广播会有信息泄露,信息干扰等问题,而且开销也比较大,因此全局广播并不适合这种情况。 也许有人会说可以用本地广播,然而,本地广播目前来说并不是很好的选择。
Google官方也在 LocalBroadcastManager 的说明里面建议使用LiveData替代: 原文地址
2018 年 12 月 17 日
版本 1.1.0-alpha01 中将弃用 androidx.localbroadcastmanager。
原因
LocalBroadcastManager 是应用级事件总线,在您的应用中使用了层违规行为;任何组件都可以监听来自其他任何组件的事件。 它继承了系统 BroadcastManager 不必要的用例限制;开发者必须使用 Intent,即使对象只存在且始终存在于一个进程中。由于同一原因,它未遵循功能级 BroadcastManager。 这些问题同时出现,会对开发者造成困扰。
替换
您可以将 LocalBroadcastManager 替换为可观察模式的其他实现。合适的选项可能是 LiveData 或被动流,具体取决于您的用例。
更明显的原因是,本地广播好像并不支持跨进程~
- 背景 跨进程相对来说还比较好实现,但是有的时候用户会有跨应用的需求,其实这个也是IPC范畴,为什么单独提出来呢? 因为跨应用涉及信息安全,权限校验问题,开放给其他应用,但是同时又要兼顾不被非法滥用。 因为数据只是进程内共享的,跨应用时,粘性事件将失效,如果要保持和单进程一样支持粘性事件,需要做特殊处理。
- 背景 一个好的事件总线需要很好的兼容,不同事件应该有个很好的管理,不会造成冲突,事件可以进行多种配置,如某事件是否支持跨进程,是否激活,属于什么分组等等。
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
def version = "2.0.0"
dependencies {
implementation "com.github.codyer.ElegantBus:core:$version" // 不需要跨进程时使用
// implementation "com.github.codyer.ElegantBus:ipc-binder:$version" // 跨进程时使用(方式1:binder 实现,已经包含 core)
// implementation "com.github.codyer.ElegantBus:ipc-aidl:$version" // 跨进程时使用(方式2:aidl 实现,已经包含 core)
// implementation "com.github.codyer.ElegantBus:ipc-messenger:$version" // 跨进程时使用(方式3:messenger 实现,已经包含 core)
// implementation "com.github.codyer.ElegantBus:ipc-provider:$version" // 跨进程时使用(方式3:contentProvider 实现,已经包含 core)
// annotationProcessor "com.github.codyer.ElegantBus:compiler:$version"// 需要事件自动管理时使用
}
manifestPlaceholders = [
BUS_SUPPORT_MULTI_APP : true,// 是否支持跨App
BUS_MAIN_APPLICATION_ID: "com.example.bus" // 肯定会被安装的主app的applicationId
]
对应子App使用对应子引用
// implementation "com.github.codyer.ElegantBus:ipc-binder_sub:$version" // 跨进程时使用(方式1:binder 实现,已经包含 core)
// implementation "com.github.codyer.ElegantBus:ipc-aidl_sub:$version" // 跨进程时使用(方式2:aidl 实现,已经包含 core)
// implementation "com.github.codyer.ElegantBus:ipc-messenger_sub:$version" // 跨进程时使用(方式3:messenger 实现,已经包含 core)
// implementation "com.github.codyer.ElegantBus:ipc-provider_sub:$version" // 跨进程时使用(方式3:contentProvider 实现,已经包含 core)
为了App安全性,必须使用相同的密钥签名的App才可以设置为一个公用组,否则Debug模式下会抛出异常,Release模式下会输出 error 信息。
public class BusApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ElegantBus.setDebug(true);// 可以打开日志开关
ElegantBusX.supportMultiProcess(this);
}
@Override
public void onTerminate() {
ElegantBusX.stopSupportMultiProcess();
super.onTerminate();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ElegantBusX.fixHighLevelAndroid(this, () -> {
// 这里可以提示用户开启相关权限
ElegantLog.e("you should accept the request !");
// 返回true意味着会一直请求直到用户授权
return true;
});
}
最简单方式就是直接一句
ElegantBus.getDefault("EventA").post(new Object());
ElegantBus.getDefault("EventA").post("eventA");
ElegantBus.getDefault("EventA").post(888888);
可以在任何线程发送都是OK的,考虑大部分是没有跨进程需求的,所以这里默认,这种最简单的方式,这个事件 EventA
是不支持跨进程的。
如果要进行跨进程可以使用重载函数进行设置,重载函数如下:
ElegantBus.getDefault(String group, String event, Class<T> type, boolean multiProcess);
接收事件也很简单:
- 常规事件
生命周期相关的事件,只有页面处于激活状态才会收到事件,如果在页面非激活状态时有事件发生,等页面激活(OnResume)时会收到事件。
ElegantBus.getDefault("EventA").observe(this, new ObserverWrapper<Object>() {
@Override
public void onChanged(final Object value) {
ElegantLog.d(value.toString());
}
});
- 粘性事件
如果观察之前有事件发生,也可以收到事件,eg:A页面发送事件,打开B页面,B页面开始观察,用粘性事件也可以收到。
ElegantBus.getDefault("EventA").observeSticky(this, new ObserverWrapper<Object>() {
@Override
public void onChanged(final Object value) {
ElegantLog.d(value.toString());
}
});
- 常驻事件
和生命周期无关,无论页面是否在激活状态,都可以收到事件,前提是页面已经打开了。
ObserverWrapper<Object> foreverObserverWrapper;
ElegantBus.getDefault("EventA").observeForever(foreverObserverWrapper = new ObserverWrapper<Object>() {
@Override
public void onChanged(final Object value) {
ElegantLog.d(value.toString());
}
});
// 常驻事件要自己取消注册,避免内存泄露
ElegantBus.getDefault("EventA").removeObserver(foreverObserverWrapper);
- 其实普通事件和常驻事件都支持粘性事件
只要创建 ObserverWrapper 时设置 sticky = true 就可以; ElegantBus 提供了默认构造函数如下:参数true 表示粘性事件
new ObserverWrapper<Object>(true) {
@Override
public void onChanged(Object value) {}
})
- 可以发现,上面的方式,接收的数据类型是 Object 的,因此,只要是同名的事件,无论发送的是什么类型,观察者都可以接收到。 为了对事件进行统一管理,防止事件冲突,事件大小写等拼写错误带来的问题,个人不建议直接使用这种方式
推荐使用事件定义方式
- 先上例子
@EventGroup(value = "TestScope", active = true)
public class EventDefine {
@Event(description = "eventInt 事件测试", multiProcess = false, active = true)
Integer eventInt;
@Event(description = "eventString 事件测试", multiProcess = true, active = true)
String eventString;
@Event(description = "eventBean 事件测试", multiProcess = true, active = true)
JavaBean eventBean;
}
其实事件定义只用到两个注解
1)、@EventGroup 使用在 class 上,定义事件分组名
,是否激活
2)、@Event 使用在变量上,定义具体 事件描述
,是否激活
,是否支持多进程
定义完注解后,通过前面导入的注解处理器 annotationProcessor ,ElegantBus 会自动生成以 EventGroup 定义的分组名的事件总线
例如上面的定义就会生成一个 TestScopeBus
然后我们所有地方就可以直接使用这个事件总线进行事件管理。
- 发送事件
TestScopeBus.eventInt().post(888);
TestScopeBus.eventString().post("新字符串");
TestScopeBus.eventBean().post(new JavaBean());
- 接收事件
TestScopeBus.eventInt().observe(owner, new ObserverWrapper<Integer>() {
@Override
public void onChanged(final Integer value) {
...
}
});
默认事件是在主线程回调的,如果想在非主线程回调,设置 ObserverWrapper.uiTread = false,同时提供默认构造函数设置是否在UI线程回调。
-
如需下载代码运行,注意替换gradle.properties 里面的对应字段 :LOCAL_REPOSITORY=file://E://local-maven
-
为了 ElegantBus 更好的为大家提供服务,更好的兼容性,我特意做了很多场景的测试,可能会有覆盖不到的,如果遇到问题,欢迎留言评论
-
测试项目地址: ElegantBus-example
-
老版本请查看分支 v1.0.0 老版本说明
-
更详细说明
- 2.2.3 1、新增binder多进程支持(其实Messenger是基于AIDL,AIDL是基于binder,最终都是binder,因此提供直接使用Binder方式);2、增加服务意外死亡监听逻辑
- 2.2.2 增加对sticky事件进行类似屏障处理,通过调用resetSticky,类似设置屏障,之前发送的消息将被屏蔽,确保后面增加的sticky观察者不会收到屏障之前的消息。但是在这之前添加的监听依然可以正常接收到之前发送的消息。
- 2.2.1 增加对事件泛型定义的支持,eg:
@Event(value = "eventMap 泛型测试", multiProcess = true)
Map<String, List<String>> eventMap;
- 2.2.0 稳定版本
如果想了解更多设计细节,可以参考简书上的说明: 如何优雅的使用LiveData实现一套EventBus(事件总线)
- 3.0.0 优先级功能支持 使用时创建 ObserverWrapper 时通过构造函数支持传优先级数字
- 1)、数字越大优先级越高
- 2)、优先级高的先收到消息
- 3)、引用类型的数据可能被高优先级的改变
- 3.1.3
- 1)、解决跨进程高gradle版本编译问题
- 2)、优化跨进程json处理
- 3)、优化性能
- 3.3.2
- 1)、新增contentProvider方式
- 2)、所有方式支持跨进程,跨App时数据防丢功能
- 3)、Fix 魔改系统无法调用主服务问题问题,具体参见新增说明使用说明第五条
跨进程使用contentProvider指定发送,去中心化,定义跨进程时需要指定送达进程包含哪些applicationId,多进程APP需要指明:other