codyer / ElegantBus

🔥🔥Android 平台,基于LivaData的EventBus,无侵入,更优雅,支持跨进程,跨应用粘性事件,优先级,自定义事件等功能。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ElegantBus

ElegantBus 是一款 Android 平台,基于LivaData的消息总线框架,这是一款非常 优雅 的消息总线框架。

如果对 ElegantBus 的实现过程,以及考虑点感兴趣的可以看看前几节自吹

如果只是想先使用的,可以跳过,直接到跳到使用说明

和常见LivaData实现的EventBus比较

消息总线 去除反射 不入侵系统包名 进程内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范畴,为什么单独提出来呢? 因为跨应用涉及信息安全,权限校验问题,开放给其他应用,但是同时又要兼顾不被非法滥用。 因为数据只是进程内共享的,跨应用时,粘性事件将失效,如果要保持和单进程一样支持粘性事件,需要做特殊处理。

挑战五 : 兼容性,简洁性

  • 背景 一个好的事件总线需要很好的兼容,不同事件应该有个很好的管理,不会造成冲突,事件可以进行多种配置,如某事件是否支持跨进程,是否激活,属于什么分组等等。

使用说明

(一)ElegantBus 接入配置

1、项目级别gradle添加依赖 目前使用的是 jitPack

allprojects {
    repositories {
    ...
    maven { url 'https://jitpack.io' }
}
}

2、在应用 gradle 文件中添加 ElegantBus 最新版本依赖

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"// 需要事件自动管理时使用
}
如果不需要跨进程,以上两步配置就可以了,如果需要跨进程,第二步选择一个跨进程的方式,并添加第三步配置,且设置第四步。

3、在应用 gradle 文件中的 manifestPlaceholders 配置是否支持跨 App,以及主 App 的 applicationId

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 信息。

4、分别在应用的 Application 的 onCreate 和 onTerminate 方法中添加开始支持多进程和结束多进程

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

5、某些机型无法正常打开主应用进程,导致跨App功能异常,需要主动在子应用启动activity中添加以下代码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ElegantBusX.fixHighLevelAndroid(this, () -> {
        // 这里可以提示用户开启相关权限
            ElegantLog.e("you should accept the request !");
            // 返回true意味着会一直请求直到用户授权
            return true;
        });
    }

以上几步就完成了使用 ElegantBus 的全部配置,下面进入使用环节

(二)ElegantBus 使用说明

1、 发送事件

最简单方式就是直接一句

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);
以下说的激活状态指页面处于RESUMED情况

2、 接收事件

接收事件也很简单:

  • 常规事件

生命周期相关的事件,只有页面处于激活状态才会收到事件,如果在页面非激活状态时有事件发生,等页面激活(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) {
		...
	}
});

事件回调在非UI线程执行

默认事件是在主线程回调的,如果想在非主线程回调,设置 ObserverWrapper.uiTread = false,同时提供默认构造函数设置是否在UI线程回调。

欢迎 Star 和提交 Issue

  • 如需下载代码运行,注意替换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 魔改系统无法调用主服务问题问题,具体参见新增说明使用说明第五条

TODO

跨进程使用contentProvider指定发送,去中心化,定义跨进程时需要指定送达进程包含哪些applicationId,多进程APP需要指明:other

About

🔥🔥Android 平台,基于LivaData的EventBus,无侵入,更优雅,支持跨进程,跨应用粘性事件,优先级,自定义事件等功能。

License:Apache License 2.0


Languages

Language:Java 99.5%Language:AIDL 0.5%