国庆假期想着闲着也是闲着,就想着来深入了解下几个常用的开源库😁😁,看下其实现原理和源码,进行总结并输出成文章。初定的目标是 EventBus、ARouter、LeakCanary、Glide、Coil、Retrofit、OkHttp 等几个。目前已经完成了部分,在之后的几天里会将文章陆续发布出来😁😁
原文地址:https://juejin.im/user/923245496518439/posts
上一篇文章中对 EventBus 的源码进行了一次全面解析,原理懂得了,那么就也需要进行一次实战才行。对于一个优秀的第三方库,开发者除了要学会如何使用外,更有难度的用法就是去了解实现原理、懂得如何改造甚至自己实现。本篇文章就来自己动手实现一个 EventBus,不求功能多齐全,就来实现简单的注册、反注册、发送消息、接收消息这些功能即可😁😁
先来看下最终的实现效果
对于以下两个监听者:EasyEventBusActivity
和 EasyBusEventTest
,通过标注 @Event
注解来修饰监听方法,然后使用 EasyEventBus
这个自定义类来进行注册、反注册和发送消息
/**
* 作者:leavesC
* 时间:2020/10/2 13:14
* 描述:
* GitHub:https://github.com/leavesC
*/
class EasyEventBusActivity : BaseActivity() {
private val eventTest = EasyBusEventTest()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_easy_event_bus)
EasyEventBus.register(this)
eventTest.register()
btn_postString.setOnClickListener {
EasyEventBus.post("Hello")
}
btn_postBean.setOnClickListener {
EasyEventBus.post(HelloBean("hi"))
}
}
@Event
fun stringFun(msg: String) {
showToast("$msg EasyEventBusActivity")
}
@Event
fun benFun(msg: HelloBean) {
showToast("${msg.data} EasyEventBusActivity")
}
override fun onDestroy() {
super.onDestroy()
EasyEventBus.unregister(this)
eventTest.unregister()
}
}
class EasyBusEventTest {
@Event
fun stringFun(msg: String) {
showToast("$msg EasyBusEventTest")
}
@Event
fun benFun(msg: HelloBean) {
showToast("${msg.data} EasyBusEventTest")
}
fun register() {
EasyEventBus.register(this)
}
fun unregister() {
EasyEventBus.unregister(this)
}
}
data class HelloBean(val data: String)
使用起来和真正的 EvnetBus 差不多 😁😁,虽然实际上是要简陋很多的~~
image最终自定义的 EasyEventBus 也只有五十行左右的代码量,仅向外部提供了三个方法用于进行注册、反注册和发送消息
/**
* 作者:leavesC
* 时间:2020/10/3 11:44
* 描述:
* GitHub:https://github.com/leavesC
*/
object EasyEventBus {
private val subscriptions = mutableSetOf<Any>()
private const val PACKAGE_NAME = "github.leavesc.easyeventbus"
private const val CLASS_NAME = "EventBusInject"
private const val CLASS_PATH = "$PACKAGE_NAME.$CLASS_NAME"
private val clazz = Class.forName(CLASS_PATH)
//通过反射生成 EventBusInject 对象
private val instance = clazz.newInstance()
@Synchronized
fun register(subscriber: Any) {
subscriptions.add(subscriber)
}
@Synchronized
fun unregister(subscriber: Any) {
subscriptions.remove(subscriber)
}
@Synchronized
fun post(event: Any) {
subscriptions.forEach { subscriber ->
val subscriberInfo =
getSubscriberInfo(subscriber.javaClass)
if (subscriberInfo != null) {
val methodList = subscriberInfo.methodList
methodList.forEach { method ->
if (method.eventType == event.javaClass) {
val declaredMethod = subscriber.javaClass.getDeclaredMethod(
method.methodName,
method.eventType
)
declaredMethod.invoke(subscriber, event)
}
}
}
}
}
//通过反射调用 EventBusInject 的 getSubscriberInfo 方法
private fun getSubscriberInfo(subscriberClass: Class<*>): SubscriberInfo? {
val method = clazz.getMethod("getSubscriberInfo", Class::class.java)
return method.invoke(instance, subscriberClass) as? SubscriberInfo
}
}
一、需要做什么
这里先来想下这个自定义的 EasyEventBus 应该实现什么功能,以及怎么实现
EasyEventBus 的核心重点就在于其通过注解处理器生成辅助文件这个过程,这个过程使用者是感知不到的,这块逻辑也只会在编译阶段被触发到。我们希望在编译阶段就能够拿到所有声明了 @Event
的方法,免得在运行时才来反射。即在编译阶段就希望能够生成以下的辅助文件:
/**
* 这是自动生成的代码 by leavesC
*/
public class EventBusInject {
private static final Map<Class<?>, SubscriberInfo> subscriberIndex = new HashMap<Class<?>, SubscriberInfo>();
{
List<EventMethodInfo> eventMethodInfoList = new ArrayList<EventMethodInfo>();
eventMethodInfoList.add(new EventMethodInfo("stringFun", String.class));
eventMethodInfoList.add(new EventMethodInfo("benFun", HelloBean.class));
SubscriberInfo subscriberInfo = new SubscriberInfo(EasyBusEventTest.class, eventMethodInfoList);
putIndex(subscriberInfo);
}
{
List<EventMethodInfo> eventMethodInfoList = new ArrayList<EventMethodInfo>();
eventMethodInfoList.add(new EventMethodInfo("stringFun", String.class));
eventMethodInfoList.add(new EventMethodInfo("benFun", HelloBean.class));
SubscriberInfo subscriberInfo = new SubscriberInfo(EasyEventBusActivity.class, eventMethodInfoList);
putIndex(subscriberInfo);
}
private static final void putIndex(SubscriberInfo info) {
subscriberIndex.put(info.getSubscriberClass(), info);
}
public final SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
return subscriberIndex.get(subscriberClass);
}
}
可以看到,subscriberIndex
中存储了所有的监听方法的签名信息,在应用运行时我们我们只需要通过 getSubscriberInfo
方法就可以拿到 subscriberClass
的所有监听方法
最后,还需要向外提供一个 API 调用入口,即上面贴出来的自定义的 EasyEventBus 这个自定义类,是提供给使用者运行时调用的,在有消息需要发送的时候通过外部传入的 subscriberClass
从 EventBusInject
取出所有监听方法进行反射回调
所以,EasyEventBus 逻辑上会拆分为两个 moudle:
- event-api。向外暴露 API 调用入口
- evnet-processor。不对外暴露,只在编译阶段生效
二、注解处理器
首先,我们需要提供一个注解对监听方法进行标记
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class Event
然后,我们在编译阶段需要预先把所有监听方法抽象保存起来,所以就需要定义两个 JavaBean 来作为承载体
/**
* 作者:leavesC
* 时间:2020/10/3 17:33
* 描述:
* GitHub:https://github.com/leavesC
*/
data class EventMethodInfo(val methodName: String, val eventType: Class<*>)
data class SubscriberInfo(
val subscriberClass: Class<*>,
val methodList: List<EventMethodInfo>
)
然后声明一个 EasyEventBusProcessor
类继承于 AbstractProcessor
,由编译器在编译阶段传入我们关心的代码元素
/**
* 作者:leavesC
* 时间:2020/10/3 15:55
* 描述:
* GitHub:https://github.com/leavesC
*/
class EasyEventBusProcessor : AbstractProcessor() {
companion object {
private const val PACKAGE_NAME = "github.leavesc.easyeventbus"
private const val CLASS_NAME = "EventBusInject"
private const val DOC = "这是自动生成的代码 by leavesC"
}
private lateinit var elementUtils: Elements
private val methodsByClass = LinkedHashMap<TypeElement, MutableList<ExecutableElement>>()
override fun init(processingEnvironment: ProcessingEnvironment) {
super.init(processingEnvironment)
elementUtils = processingEnv.elementUtils
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
//只需要处理 Event 注解
return mutableSetOf(Event::class.java.canonicalName)
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.RELEASE_8
}
···
}
通过 collectSubscribers
方法拿到所有的监听方法,保存到 methodsByClass
中,同时需要对方法签名进行校验:只能是实例方法,且必须是 public 的,最多且只能包含一个入参参数
override fun process(
set: Set<TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
val messager = processingEnv.messager
collectSubscribers(set, roundEnvironment, messager)
if (methodsByClass.isEmpty()) {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Event annotations found")
} else {
···
}
return true
}
private fun collectSubscribers(
annotations: Set<TypeElement>,
env: RoundEnvironment,
messager: Messager
) {
for (annotation in annotations) {
val elements = env.getElementsAnnotatedWith(annotation)
for (element in elements) {
if (element is ExecutableElement) {
if (checkHasNoErrors(element, messager)) {
val classElement = element.enclosingElement as TypeElement
var list = methodsByClass[classElement]
if (list == null) {
list = mutableListOf()
methodsByClass[classElement] = list
}
list.add(element)
}
} else {
//@Event 只能用于修改方法
messager.printMessage(
Diagnostic.Kind.ERROR,
"@Event is only valid for methods",
element
)
}
}
}
}
/**
* 校验方法签名是否合法
*/
private fun checkHasNoErrors(element: ExecutableElement, messager: Messager): Boolean {
//不能是静态方法
if (element.modifiers.contains(Modifier.STATIC)) {
messager.printMessage(Diagnostic.Kind.ERROR, "Event method must not be static", element)
return false
}
//必须是 public 方法
if (!element.modifiers.contains(Modifier.PUBLIC)) {
messager.printMessage(Diagnostic.Kind.ERROR, "Event method must be public", element)
return false
}
//方法最多且只能包含一个参数
val parameters = element.parameters
if (parameters.size != 1) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Event method must have exactly 1 parameter",
element
)
return false
}
return true
}
然后,再来生成 subscriberIndex
这个静态常量,以及对应的静态方法块、putIndex
方法
//生成 subscriberIndex 这个静态常量
private fun generateSubscriberField(): FieldSpec {
val subscriberIndex = ParameterizedTypeName.get(
ClassName.get(Map::class.java),
getClassAny(),
ClassName.get(SubscriberInfo::class.java)
)
return FieldSpec.builder(subscriberIndex, "subscriberIndex")
.addModifiers(
Modifier.PRIVATE,
Modifier.STATIC,
Modifier.FINAL
)
.initializer(
"new ${"$"}T<Class<?>, ${"$"}T>()",
HashMap::class.java,
SubscriberInfo::class.java
)
.build()
}
//生成静态方法块
private fun generateInitializerBlock(builder: TypeSpec.Builder) {
for (item in methodsByClass) {
val methods = item.value
if (methods.isEmpty()) {
break
}
val codeBuilder = CodeBlock.builder()
codeBuilder.add(
"${"$"}T<${"$"}T> eventMethodInfoList = new ${"$"}T<${"$"}T>();",
List::class.java,
EventMethodInfo::class.java,
ArrayList::class.java,
EventMethodInfo::class.java
)
methods.forEach {
val methodName = it.simpleName.toString()
val eventType = it.parameters[0].asType()
codeBuilder.add(
"eventMethodInfoList.add(new EventMethodInfo(${"$"}S, ${"$"}T.class));",
methodName,
eventType
)
}
codeBuilder.add(
"SubscriberInfo subscriberInfo = new SubscriberInfo(${"$"}T.class, eventMethodInfoList); putIndex(subscriberInfo);",
item.key.asType()
)
builder.addInitializerBlock(
codeBuilder.build()
)
}
}
//生成 putIndex 方法
private fun generateMethodPutIndex(): MethodSpec {
return MethodSpec.methodBuilder("putIndex")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(Void.TYPE)
.addParameter(SubscriberInfo::class.java, "info")
.addCode(
CodeBlock.builder().add("subscriberIndex.put(info.getSubscriberClass() , info);")
.build()
)
.build()
}
然后,再来生成 getSubscriberInfo
这个公开方法,用于运行时调用
//生成 getSubscriberInfo 方法
private fun generateMethodGetSubscriberInfo(): MethodSpec {
return MethodSpec.methodBuilder("getSubscriberInfo")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.returns(SubscriberInfo::class.java)
.addParameter(getClassAny(), "subscriberClass")
.addCode(
CodeBlock.builder().add("return subscriberIndex.get(subscriberClass);")
.build()
)
.build()
}
完成以上方法的定义后,就可以在 process
完成 EventBusInject
整个类文件的构建了
override fun process(
set: Set<TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
val messager = processingEnv.messager
collectSubscribers(set, roundEnvironment, messager)
if (methodsByClass.isEmpty()) {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Event annotations found")
} else {
val typeSpec = TypeSpec.classBuilder(CLASS_NAME)
.addModifiers(Modifier.PUBLIC)
.addJavadoc(DOC)
.addField(generateSubscriberField())
.addMethod(generateMethodPutIndex())
.addMethod(generateMethodGetSubscriberInfo())
generateInitializerBlock(typeSpec)
val javaFile = JavaFile.builder(PACKAGE_NAME, typeSpec.build())
.build()
try {
javaFile.writeTo(processingEnv.filer)
} catch (e: Throwable) {
e.printStackTrace()
}
}
return true
}
三、EasyEventBus
EasyEventBus 的逻辑就很简单了,主要是通过反射来生成 EventBusInject
对象,拿到 subscriber
关联的 SubscriberInfo
,然后在有消息被 Post 出来的时候进行遍历调用即可
/**
* 作者:leavesC
* 时间:2020/10/3 11:44
* 描述:
* GitHub:https://github.com/leavesC
*/
object EasyEventBus {
private val subscriptions = mutableSetOf<Any>()
private const val PACKAGE_NAME = "github.leavesc.easyeventbus"
private const val CLASS_NAME = "EventBusInject"
private const val CLASS_PATH = "$PACKAGE_NAME.$CLASS_NAME"
private val clazz = Class.forName(CLASS_PATH)
//通过反射生成 EventBusInject 对象
private val instance = clazz.newInstance()
@Synchronized
fun register(subscriber: Any) {
subscriptions.add(subscriber)
}
@Synchronized
fun unregister(subscriber: Any) {
subscriptions.remove(subscriber)
}
@Synchronized
fun post(event: Any) {
subscriptions.forEach { subscriber ->
val subscriberInfo =
getSubscriberInfo(subscriber.javaClass)
if (subscriberInfo != null) {
val methodList = subscriberInfo.methodList
methodList.forEach { method ->
if (method.eventType == event.javaClass) {
val declaredMethod = subscriber.javaClass.getDeclaredMethod(
method.methodName,
method.eventType
)
declaredMethod.invoke(subscriber, event)
}
}
}
}
}
//通过反射调用 EventBusInject 的 getSubscriberInfo 方法
private fun getSubscriberInfo(subscriberClass: Class<*>): SubscriberInfo? {
val method = clazz.getMethod("getSubscriberInfo", Class::class.java)
return method.invoke(instance, subscriberClass) as? SubscriberInfo
}
}
四、结尾
文本实现的 EasyEventBus 挺简陋的😁😁,因为我的想法也只是通过自己动手来加深对 EventBus 的理解而已,这里也提供上述代码的 GitHub 链接:Demo
网友评论