ElegantBus

Project Url: codyer/ElegantBus
Introduction: 🔥🔥Android 平台,基于 LivaData 的 EventBus,无侵入,更优雅,支持跨进程,跨应用粘性事件,自定义事件等功能。
More: Author   ReportBugs   
Tags:

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

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

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

和常见 LivaData 实现的 EventBus 比较

消息总线 去除反射 不入侵系统包名 进程内 Sticky 跨进程 Sticky 跨 APP Sticky 事件可配置化 线程分发 消息分组 跨 App 安全考虑 常驻事件 Sticky
LiveEventBus :x: :x: :white_check_mark: :x: :x: :x: :x: :x: :x: :x:
ElegantBus :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:

来龙去脉

自吹

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

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools