请点赞关注,你的反对对我意义重大。
🔥 Hi,我是小彭。本文已收录到 GitHub · AndroidFamily 中。这里有 Android 进阶成长常识体系,有气味相投的敌人,关注公众号 [彭旭锐] 带你建设外围竞争力。
前言
大家好,我是小彭。2 年前,咱们在 为了组件化革新学习十几家大厂的技术博客 这篇文章里收集过各大厂的组件化计划。其中,有美团收银团队分享的组件化总线框架 modular-event
让咱们印象粗浅。然而,美团并未将该框架开源,咱们只能画饼充饥。
在学习和借鉴美团 modular-event
计划中很多优良的设计思维后,我亦发现计划中仍然存在不统一危险和有余,故我决定对计划进行改良并向社区开源。我的项目主页为 Github · ModularEventBus,演示 Demo 可间接下载:
Demo apk。
欢送提 Issue 帮忙修复缺点,欢送提 Pull Request 减少新的 Feature,有用请点赞给 Star,给小彭一点创作的能源,谢谢。
这篇文章是 组件化系列文章第 5 篇,相干 Android 工程化专栏残缺文章列表:
一、Gradle 根底:
- 1、Gradle 根底:Wrapper、Groovy、生命周期、Project、Task、增量
- 2、Gradle 插件:Plugin、Extension 扩大、NamedDomainObjectContainer、调试
- 3、Gradle 依赖治理
- 4、Maven 公布:SHAPSHOT 快照、uploadArchives、Nexus、AAR
- 5、Gradle 插件案例:EasyPrivacy、so 文件适配 64 位架构、ABI
二、AGP 插件:
- 1、AGP 构建过程
- 2、AGP 罕用配置项:Manifest、BuildConfig、buildTypes、壳工程、环境切换
- 3、APG Transform:AOP、TransformTask、增量、字节码、Dex
- 4、AGP 代码混同:ProGuard、R8、Optimize、Keep、组件化
- 5、APK 签名:认证、完整性、v1、v2、v3、Zip、Wallet
- 6、AGP 案例:多渠道打包
三、组件化开发:
- 1、计划积攒:有赞、蘑菇街、失去、携程、支付宝、手淘、爱奇艺、微信、美团
- 2、组件化架构根底
- 3、ARouter 源码剖析
- 4、组件化案例:通用计划
- 5、组件化案例:组件化事件总线框架(本文)
- 6、组件化案例:组件化 Key-Value 框架
四、AOP 面向切面编程:
- 1、AOP 根底
- 2、Java 注解
- 3、Java 注解处理器:APT、javac
- 4、Java 动静代理:代理模式、Proxy、字节码
- 5、Java ServiceLoader:服务发现、SPI、META-INF
- 6、AspectJ 框架:Transform
- 7、Javassist 框架
- 8、ASM 框架
- 9、AspectJ 案例:限度按钮点击抖动
五、相干计算机根底
- 1、Base64 编码
- 2、平安传输:加密、摘要、签名、CA 证书、防窃听、完整性、认证
1. 意识事件总线
1.1 事件总线的长处
事件总线框架最大的长处是”解耦“,即事件发布者与事件订阅者的解耦,事件的发布者不须要关怀是否有人订阅该事件,也不须要关怀是谁订阅该事件,代码耦合度较低。因而,事件总线框架更适宜作为全局的事件通信计划,或者组件间通信的辅助计划。
1.2 事件总线的毛病
然而,成也萧何败萧何。有人感觉事件总线好用,亦有人感觉事件总线不好用,归根结底还是因为事件总线太容易被滥用了,用时一时爽,保护火葬场。我将事件总线框架存在的问题概括为以下 5 种常见问题:
- 1、音讯难溯源: 在浏览源码的过程中,如果须要查找公布事件或订阅事件的中央,只能通过查找事件援用的形式进行溯源,增大了了解代码逻辑的难度。特地是当我的项目中到处是长期事件时,难度会大大增加;
- 2、长期事件滥用: 因为框架对事件定义没有强制束缚,开发者能够随便地在我的项目的各个角落定义事件。导致整个我的项目都是长期事件飞来飞去,增大前期保护的难度;
- 3、数据类型转换谬误: LiveDataBus 等事件总线框架须要开发者手动输出事件数据类型,当订阅方与发送方应用不同的数据类型时,会产生类型转换谬误。在产生事件命名抵触时,出错的概率会大大增加,存在隐患;
- 4、事件命名反复: 因为框架对事件命名没有强制束缚,不同组件有可能定义重名的事件,产生逻辑谬误。如果重名的事件还应用了不同的数据类型,还会呈现类型转换谬误,存在隐患;
- 5、事件命名忽略: 与”事件命名反复“相似,因为框架对事件命名没有查看,有可能呈现开发者复制粘贴后遗记批改事件变量值的问题,或者变量值拼写错误(例如
login_success
拼写为login_succese
),那么订阅方将永远收不到事件。
1.3 ModularEventBus 的解决方案
ModularEventBus 组件化事件总线框架的长处是: 在放弃发布者与订阅者的解耦的劣势下,解决上述事件总线框架中存在的通病。 具体通过以下 5 个伎俩实现:
- 1、事件申明聚合: 发布者和订阅者只能应用预约义的事件,严格禁止应用长期事件,事件须要依照约定聚合定义在一个文件中(解决长期事件滥用问题);
- 2、辨别不同组件的同名事件: 在定义事件时须要指定事件所属
moduleName
,框架主动应用"[moduleName]$$[eventName]"
作为最终的事件名(解决事件命名反复问题); - 3、事件数据类型申明: 在定义事件时须要指定事件的数据类型,框架主动应用该数据类型发送和订阅事件(解决数据类型转换谬误问题);
- 4、接口强束缚: 运行时应用事件类公布和订阅事件,框架主动应用事件定义的事件名和数据类型,而不须要手动输出事件名和数据类型(解决事件命名命名谬误);
- 5、APT 生成接口类: 框架在编译时应用 APT 注解处理器主动生成事件接口类。
1.4 与美团 modular-event 比照有哪些什么不同?
-
modular-event 应用动态常量定义事件,为什么 ModularEventBus 用接口定义事件?
美团 modular-event 应用常量引入了反复信息,存在不统一危险。例如开发者复制一行常量后,只批改常量名但遗记批改值,这种谬误往往很难被发现。而 ModularEventBus 应用办法名作为事件名,办法返回值作为事件数据类型,不会引入反复信息且更加简洁。
modular-event 事件定义
-
modular-event 应用动静代理,为什么 ModularEventBus 不须要?
美团 modular-event 应用动静代理 API 对立接管了事件的公布和订阅,但思考到这部分代理逻辑非常简单(获取事件名并交给 LiveDataBus 实现后续的公布和订阅逻辑),且框架自身曾经引入了编译时 APT 技术,齐全能够在编译时生成这部分代理逻辑,没必要应用动静代理 API。
-
更多个性反对:
此外 ModularEventBus 还反对生成事件文档、空数据拦挡、泛型事件、主动革除闲暇事件等个性。
2. ModularEventBus 能做什么?
ModularEventBus 是一款帮忙 Android App 解决事件总线滥用问题的框架,亦可作为组件化基础设施。 其解决方案是通过注解定义事件,由编译时 APT 注解处理器进行合法性检查和主动生成事件接口,以实现对事件定义、公布和订阅的强束缚。
2.1 常见事件总线框架比照
以下从多个维度比照常见的事件总线框架(✅ 良好反对、✔️ 反对、❌ 不反对):
事件总线 | ModularEventBus | modular-event | SmartEventBus | LiveEventBus | LiveDataBus | EventBus | RxBus |
---|---|---|---|---|---|---|---|
开发者 | @彭旭锐 | @美团 | @JeremyLiao | @JeremyLiao | / | @greenrobot | / |
Github Star | 0 | 未开源 | 146 | 3.4k | / | 24.1k | / |
生成事件文档 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
空数据拦挡 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
无数据事件 | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
泛型事件 | ✅ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
主动革除闲暇事件 | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
事件强束缚 | ✅ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
生命周期感知 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
提早发送事件 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
有序接管事件 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
订阅 Sticky 事件 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
革除 Sticky 事件 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
移除事件 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
线程调度 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
跨过程 / 跨 App | ❌(可反对) | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
要害原理 | APT+ 动态代理 | APT+ 动静代理 | APT+ 动态代理 | LiveData | LiveData | APT | RxJava |
2.2 ModularEventBus 个性一览
1、事件强束缚
✅ 反对零配置疾速应用;
✅ 反对 APT 注解处理器主动生成事件接口类;
✅ 反对编译时合法性校验和正告提醒;
✅ 反对生成事件文档;
✅ 反对增量编译;
2、Lifecycle 生命周期感知
✅ 内置基于 LiveData
的 LiveDataBus;
✅ 反对主动勾销订阅,防止内存透露;
✅ 反对平安地发送事件与接管事件,防止产生空指针异样或不必要的性能损耗;
✅ 反对永恒订阅事件;
✅ 反对主动革除没有关联订阅者的闲暇 LiveData
以开释内存;
3、更多个性反对
✅ 反对 Java / Kotlin;
✅ 反对 AndroidX;
✅ 反对订阅 Sticky 粘性事件,反对移除事件;
✅ 反对 Generic 泛型事件,如 List<String>
事件;
✅ 反对拦挡空数据;
✅ 反对只公布事件不携带数据的无数据事件;
✅ 反对提早发送事件;
✅ 反对有序接管事件。
3. ModularEventBus 疾速应用
- 1、增加依赖
模块级 build.gradle
plugins {
id 'com.android.application' // 或 id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
dependencies {
// 替换成最新版本
implementation 'io.github.pengxurui:modular-eventbus-api:1.0.4'
kapt 'io.github.pengxurui:modular-eventbus-compiler:1.0.4'
...
}
- 2、定义事件数据类型(可选): 定义事件关联的数据类型,对于只公布事件而不须要携带数据的场景,能够不定义事件类型。
UserInfo.kt
data class UserInfo(val userName: String)
- 3、定义事件: 应用接口定义事件名和事件数据类型,并应用
@EventGroup
注解润饰该接口:
LoginEvents.kt
@EventGroup
interface LoginEvents {
// 事件名:login
// 事件数据类型:UserInfo
fun login(): UserInfo
// 事件名:logout
fun logout()}
- 4、执行注解处理器: 执行
Make Project
或Rebuild Project
等多种形式都能够触发注解处理器,处理器将依据事件定义主动生成相应的事件接口。例如,LoginEvents
对应的事件类为:
EventDefineOfLoginEvents.java
/**
* Auto generate code, do not modify!!!
* @see com.pengxr.sampleloginlib.events.LoginEvents
*/
@SuppressWarnings("unchecked")
public class EventDefineOfLoginEvents implements IEventGroup {private EventDefineOfLoginEvents() { }
public static IEvent<UserInfo> login() {return (IEvent<UserInfo>) (ModularEventBus.INSTANCE.createObservable("com.pengxr.sampleloginlib.events.LoginEvents$$login", UserInfo.class, false, true));
}
public static IEvent<Void> logout() {return (IEvent<Void>) (ModularEventBus.INSTANCE.createObservable("com.pengxr.sampleloginlib.events.LoginEvents$$logout", Void.class, true, false));
}
}
- 5、订阅事件: 应用
EventDefineOfLoginEvents
事件类提供的静态方法订阅事件:
订阅者示例
// 以生命周期感知模式订阅事件(不须要手动登记订阅)EventDefineOfLoginEvents.login().observe(this) { value: UserInfo? ->
// Do something.
}
// 以永恒模式订阅事件(须要手动登记订阅)EventDefineOfLoginEvents.logout().observeForever { _: Void? ->
// Do something.
}
- 6、公布事件: 应用
EventDefineOfLoginEvents
提供的静态方法公布事件:
发布者示例
EventDefineOfLoginEvents.login().post(UserInfo("XIAOPENG"))
EventDefineOfLoginEvents.logout().post(null)
- 7、增加混同规定(如果应用了 minifyEnabled true):
-dontwarn com.pengxr.modular.eventbus.generated.**
-keep class com.pengxr.modular.eventbus.generated.** {*;}
-keep @com.pengxr.modular.eventbus.facade.annotation.EventGroup class * {*;} # 可选
4. 残缺应用文档
4.1 定义事件
-
应用注解定义事件:
@EventGroup
注解:@EventGroup
注解用于定义事件组,润饰于 interface 接口上,在该类中定义的每个办法均视为一个事件定义;@Event
注解:@Event
注解用于事件组中的事件定义,亦可省略。
模板程序如下:
com.pengxr.sample.events.MainEvents.kt
// 事件组
@EventGroup
interface MainEvents {
// 事件
// @Event 能够省略
@Event
fun open(): String}
提醒: 以上即定义了一个
MainEvents
事件组,其中蕴含一个com.pengxr.sample.events.MainEvents$$open
事件且数据类型为String
类型。
亦兼容将 @EventGroup
润饰于 class 类而非 interface 接口,但会有编译时正告:Annotated @EventGroup on a class type [IllegalEvent], expected a interface. Is that really what you want?
谬误示例
@EventGroup
class IllegalEvent {fun illegalEvent() {}}
- 应用
@Ignore
注解疏忽定义: 应用@Ignore
注解能够排除事件类或事件办法,使其不被视为事件定义。
示例程序
// 能够润饰于事件组
@Ignore
@EventGroup
interface IgnoreEvent {
// 亦可润饰于事件
@Ignore
fun ignoredMethod()
fun method()}
- 应用
@Deprecated
注解提醒过期: 应用@Deprecated
注解能够标记事件为过期。与@Ignore
不同是,@Deprecated
润饰的类或办法仍然是无效的事件定义。
示例程序
// 尽管过期,但仍然是无效的事件定义
@Deprecated("Don't use it.")
@EventGroup
interface DeprecatedEvent {@Deprecated("Don't use it.")
fun deprecatedMethod()}
- 定义事件数据类型: 事件办法返回值即示意事件数据类型,反对泛型(如
List<String>
),反对不携带数据的无数据事件。以下均为非法定义:
Java 示例程序
// 事件数据类型为 String
String stringEventInJava();
// 事件数据类型为 List<String>
List<String> listEventInJava();
// 以下均视为无数据事件
void voidEventInJava1();
Void voidEventInJava2();
Kotlin 示例程序
// 事件数据类型为 String
fun stringEventInKotlin(): String
// 事件数据类型为 List<String>
fun listEventInKotlin(): List<String>
// 以下均视为无数据事件
fun voidEventInKotlin1()
fun voidEventInKotlin2(): Unit
fun voidEventInKotlin3(): Unit?
- 定义事件数据可空性: 应用
@Nullable
或@NonNull
注解示意事件数据可空性,默认为可空类型。以下均为非法定义:
Java 示例程序
@NonNull
String nonNullEventInJava();
@Nullable
String nullableEventInJava();
// 默认视为 @Nullable
String eventInJava();
Kotlin 示例程序
fun nonNullEventInKotlin(): String
// 提醒:Kotlin 编译器将返回类型上的 ? 号视为 @org.jetbrains.annotations.Nullable
fun nullableEventInKotlin(): String?
以下为反对的可空性注解:
org.jetbrains.annotations.Nullable
android.annotation.Nullable
androidx.annotation.Nullable
org.jetbrains.annotations.NotNull
android.annotation.NonNull
androidx.annotation.NonNull
- 定义主动革除事件: 反对配置在事件没有关联的订阅者时主动被革除(以开释内存),默认值为 false。能够应用
@EventGroup
注解或@Event
注解进行批改,以@Event
的取值优先。
示例程序
@EventGroup(autoClear = true)
interface MainEvents {@Event(autoClear = false)
fun normalEvent(): String
// 继承 @EventGroup 中的 autoClear 取值
fun autoClearEvent(): String}
- 定义事件所属组件名: 为防止不同组件中的事件名反复,框架主动应用
"[moduleName]$$[eventName]"
作为最终的事件名。默认应用事件组的[全限定类名]
作为moduleName
,能够应用@EventGroup
注解进行批改。
示例程序
com.pengxr.sample.events.MainEvents.kt
@EventGroup(moduleName = "main")
interface MainEvents {fun open(): String
}
提醒: 以上即定义了一个
MainEvents
事件组,其中蕴含一个main$$open
事件且数据类型为String
类型。
4.2 执行注解处理器
在实现事件定义后,执行 Make Project
或 Rebuild Project
等多种形式都能够触发注解处理器,处理器将依据事件定义主动生成相应的事件接口。例如,MainEvents
对应的事件接口为:
com.pengxr.modular.eventbus.generated.events.com.pengxr.sample.events.EventDefineOfMainEvents.java
/**
* Auto generate code, do not modify!!!
* @see com.pengxr.sample.events.MainEvents
*/
@SuppressWarnings("unchecked")
public class EventDefineOfMainEvents implements IEventGroup {private EventDefineOfMainEvents() { }
public static IEvent<String> open() {return (IEvent<String>) (ModularEventBus.INSTANCE.createObservable("main$$open", String.class, false, false));
}
}
EventDefineOfMainEvents
中的静态方法与 MainEvent
事件组中的每个事件一一对应,间接通过静态方法即可获取事件实例,而不再通过手动输出事件名字符串或事件数据类型,故可防止事件名谬误或数据类型谬误等问题。
所有的事件实例均是 IEvent
泛型接口的实现类,例如 open
事件属于 IEvent<String>
类型的事件实例。公布事件和订阅事件须要用到 IEvent
接口中定义的一系列 post 办法和 observe 办法,IEvent
接口的残缺定义如下:
IEvent.kt
interface IEvent<T> {
/**
* 公布事件,容许在子线程公布
*/
@AnyThread
fun post(value: T?)
/**
* 提早公布事件,容许在子线程公布
*/
@AnyThread
fun postDelay(value: T?, delay: Long)
/**
* 提早公布事件,在筹备公布前会查看 producer 处于沉闷状态,容许在子线程公布
*
* @param producer 发布者的 LifecycleOwner
*/
@AnyThread
fun postDelay(value: T?, delay: Long, producer: LifecycleOwner)
/**
* 公布事件,容许在子线程公布,确保订阅者依照公布程序接管事件
*/
@AnyThread
fun postOrderly(value: T?)
/**
* 以生命周期感知模式订阅事件(不须要手动登记订阅)*/
@AnyThread
fun observe(consumer: LifecycleOwner, observer: Observer<T?>)
/**
* 以生命周期感知模式粘性订阅事件(不须要手动登记订阅)*/
@AnyThread
fun observeSticky(consumer: LifecycleOwner, observer: Observer<T?>)
/**
* 以永恒模式订阅事件(须要手动登记订阅)*/
fun observeForever(observer: Observer<T?>)
/**
* 以永恒模式粘性订阅事件(须要手动登记订阅)*
* @param observer Event observer.
*/
@AnyThread
fun observeStickyForever(observer: Observer<T?>)
/**
* 登记订阅者
*/
@AnyThread
fun removeObserver(observer: Observer<T?>)
/**
* 移除事件,关联的订阅者关系也会被解除
*/
@AnyThread
fun removeEvent()}
4.3 订阅事件
应用 IEvent
接口定义的一系列 observe()
接口订阅事件,应用示例:
示例程序
// 以生命周期感知模式订阅(不须要手动登记订阅)EventDefineOfMainEvents.open().observe(this) {// do something.}
// 以生命周期感知模式、且粘性模式订阅(不须要手动登记订阅)EventDefineOfMainEvents.open().observeSticky(this) {// do something.}
val foreverObserver = Observer<String?> {// do something.}
// 以永恒模式订阅(须要手动登记订阅)EventDefineOfMainEvents.open().observeForever(foreverObserver)
// 以永恒模式,且粘性模式订阅(须要手动登记订阅)EventDefineOfMainEvents.open().observeStickyForever(foreverObserver)
// 移除观察者
EventDefineOfMainEvents.open().removeObserver(foreverObserver)
4.4 公布事件
应用 IEvent
接口定义的一系列 post()
接口公布事件,应用示例:
示例程序
// 公布事件,容许在子线程公布
EventDefineOfMainEvents.open().post("XIAO PENG")
// 提早公布事件,容许在子线程公布
EventDefineOfMainEvents.open().postDelay("XIAO PENG", 5000)
// 提早公布事件,在筹备公布前会查看 producer 处于沉闷状态,容许在子线程公布。EventDefineOfMainEvents.open().postDelay("XIAO PENG", 5000, this)
// 公布事件,容许在子线程公布,确保订阅者依照公布程序接管事件
EventDefineOfMainEvents.open().postOrderly("XIAO PENG")
// 移除事件
EventDefineOfMainEvents.open().removeEvent()
4.5 更多功能
- 生成事件文档(可选): 反对生成事件文档,须要在 Gradle 配置中开启:
模块级 build.gradle
// 须要生成事件文档的模块就减少配置:android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [
MODULAR_EVENTBUS_GENERATE_DOC: "enable",
MODULAR_EVENTBUS_MODULE_NAME : project.getName()]
}
}
}
}
文档生成门路:build/generated/source/kapt/[buildType]/com/pengxr/modular/eventbus/generated/docs/eventgroup-of-[MODULAR_EVENTBUS_MODULE_NAME].json
-
配置(可选):
- debug(Boolean): 调试模式开关;
- throwNullEventException(Boolean): 非空事件公布空数据时是否抛出
NullEventException
异样,在release
模式默认为只拦挡不抛出异样,在debug
模式默认为拦挡且抛出异样; - setEventListener(IEventListener): 全局监听接口。
示例程序
ModularEventBus.debug(true)
.throwNullEventException(true)
.setEventListener(object : IEventListener {override fun <T> onEventPost(eventName: String, event: BaseEvent<T>, data: T?) {Log.i(TAG, "onEventPost: $eventName, event = $event, data = $data")
}
})
5. 将来性能布局
- 反对跨过程 / 跨 App:LiveEventBus 框架反对跨过程 / 跨 App,将来依据应用反馈思考实现该 Feature;
- 反对替换外部 EventBus 工厂:ModularEventBus 已预设计事件总线工厂
IEventFactory
,将来依据应用反馈思考公开该 API; - 反对基于 Kotlin Flow 的 IEventFactory 工厂;
- 编译时查看在不同
@EventGroup
中设置雷同 modulaName 且雷同eventName
,但事件数据类型不同的异样。
6. 独特成长
- 欢送提 Issue 帮忙修复缺点;
- 欢送提 Pull Request 减少新的 Feature,让 ModularEventBus 变得更加弱小,你的 ID 会呈现在 Contributors 中;
- 欢送加 作者微信 与作者交换,欢送退出交换群找到气味相投的搭档
参考资料
- Android 音讯总线的演进之路:用 LiveDataBus 代替 RxBus、EventBus —— 海亮(美团)著
- Android 组件化计划及组件音讯总线 modular-event 实战 —— 海亮(美团)著
我是小彭,带你构建 Android 常识体系。技术和职场问题,请关注公众号 [彭旭锐] 私信我发问。