注解全解析2 -- 分析EventBus中的运行时注解
前言
上一篇文章我们介绍了一些注解的基础知识,必须要记住,从这篇开始我们就来看看一些常见的库是如何使用注解的。首先看一下运行时注解的使用,我们这里使用EventBus这个库来做示例。
本系列文章:
# 注解全解析1 -- 基本概念介绍
正文
EventBus的官方项目地址:github.com/greenrobot/…
而且其使用非常简单。
EventBus简单使用
这里先简单介绍一下如何使用EventBus。
创建事件
public static class MessageEvent { /* Additional fields if needed */ } 复制代码
注册订阅者,即事件收到时要执行的操作
//使用注解表明在主线程时收到事件时,会自动调用这个方法 @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { // Do something } 复制代码
注册和反注册订阅者,在Android中假如在某个页面监听事件,便是页面可见前进行注册,页面销毁时进行反注册,其实这个操作如果和Jetpack中的Lifecycle组件结合,可能就不需要手动注册和反注册了
@Override public void onStart() { super.onStart(); //在onStart中注册 EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); //在onStop中反注册 EventBus.getDefault().unregister(this); } 复制代码
发送事件
EventBus.getDefault().post(new MessageEvent()); 复制代码
上面几个步骤便是EventBus最简单的使用,但是本篇的文章重点不是这里,对于EventBus的原理,我们后面有机会再分析,我们仅仅探讨其中关于注解的地方。
运行时注解
其实仔细想一想,再结合前面一篇的内容,我们完全可以使用编译时注解也完成类似的操作,当然现在暂不考虑。
既然是运行时注解,便是在代码执行二进制文件时进行功能操作,比如EventBus这个库,涉及注解的地方就是找到当事件发出时,需要执行的方法。
同时运行时注解一般都是通过反射来完成,那么使用运行时注解也就分为2个部分,声明注解和在适当的时候解析注解:
声明注解
一想到声明注解,就要熟记那几个元注解,因为元注解是用来修饰自定义注解的,我们看一下EventBus中的@Subscribe注解的声明:
@Documented @Retention(RetentionPolicy.RUNTIME) //注解在运行时依旧有效 @Target({ElementType.METHOD}) //注解修饰在方法上 public @interface Subscribe { //注解的参数,可以设置默认值 ThreadMode threadMode() default ThreadMode.POSTING; boolean sticky() default false; int priority() default 0; } 复制代码
就这样我们便声明了一个运行时注解,我们使用注解一般就是为了方便,让代码简洁,所以这里的参数一般不要太多,在使用时直接用逗号隔开即可:
@Subscribe(threadMode = ThreadMode.MAIN, sticky = false, priority = 0) 复制代码
比如下面的代码:
@Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0) public void onEventMainThread(TestFinishedEvent event) { //在合适的时机,收到事件时会执行 } 复制代码
下面接着看如何解析注解。
解析注解
同样在前面EventBus的使用中我们已经说过,我们需要注册和反注册订阅者,这里的订阅者一般也就是Activity或者Fragment:
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } 复制代码
所以这里的逻辑便非常清晰了,当Android 应用执行到该页面时,会把该页面的类注册到EventBus系统中,然后当事件发生时,再去调用类中的相应方法。
所以直接看一下register()的代码:
//注册订阅者,这里的订阅者在Android中是Activity类或者Fragment类 public void register(Object subscriber) { //拿到订阅者的Class Class<?> subscriberClass = subscriber.getClass(); //找到订阅者的方法 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); //同步操作 synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { //订阅 subscribe(subscriber, subscriberMethod); } } } 复制代码
这里的逻辑非常简单,但是很多开发者如果不熟悉反射的话,去阅读这里的源码会觉得很困难,原因就是对这方面的API接触较少。
所以下面简单介绍一下Class类型。
Class类型
Class就是类的类型,其实这里非常好理解,现在可以完全不用考虑类的业务逻辑,只考虑类的组成部分,它是由哪些部分组成的,比如下面的代码:
//逻辑不用看,看组成部分 //类的声明部分 有继承的父类 public class TestRunnerActivity extends Activity { //类的变量 private TestRunner testRunner; private EventBus controlBus; private TextView textViewResult; //方法,带有注解 @Override //方法又可以看成由方法名、参数、注解等部分组成 public void onCreate(Bundle savedInstanceState) { // ... } //方法,带有注解 @Override protected void onResume() { super.onResume(); // ... } //带有我们想要的注解 @Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0) public void onEventMainThread(TestFinishedEvent event) { // ... } //方法 public void onClickCancel(View view) { // ... } public void onClickKillProcess(View view) { // ... } public void onDestroy() { // ... } } 复制代码
然后呢,就是把上面类的组成信息放到Class类中,所以拿到一个类的Class对象就可以做以下操作:
//包名 String getName = subscriberClass.getName(); String getSimpleName = subscriberClass.getSimpleName(); //构造函数 Constructor<?> getCon = subscriberClass.getConstructor(); Constructor<?>[] getDeclCon = subscriberClass.getDeclaredConstructors(); //字段 Field[] getFields = subscriberClass.getFields(); Field[] getDeclFields = subscriberClass.getDeclaredFields(); //方法 Method[] getM = subscriberClass.getMethods(); Method[] getDeclM = subscriberClass.getDeclaredMethods(); //直接超类的type Type getGenSuperClas = subscriberClass.getGenericSuperclass(); //当前类实现的接口 Class<?>[] getInter = subscriberClass.getInterfaces(); //修饰符 int getMod = subscriberClass.getModifiers(); 复制代码
上面代码对应方法和结果如下:
可以点击图片放大观看结果,通过这些方法我们就很容易得到一个类的信息,比如字段、方法等。
其实这么多方法记起来也不难,普通的getXXX方法是获取当前类和父类的public字段、方法,getDeclaredXXX方法是获取当前类声明的所有修饰符的字段、方法。
OK,按照思路,我们可以根据一个类的Class类型获取其中的方法,然后再找出我们需要的方法,即添加了某些特定注解的方法,我们接着看。
找到注解信息
源码很多,但是我们思路很明确,根据Class找到该类的方法,再进行处理,源码中对应的方法:
//通过反射获取需要处理的方法 private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; //获取当前类声明的所有方法 methods = findState.clazz.getDeclaredMethods(); //遍历方法 for (Method method : methods) { //获取方法的修饰符 int modifiers = method.getModifiers(); //方法必须是public修饰 if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //获取方法的参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); //由于订阅者调用该方法时,只有一个参数,所以进行筛选 if (parameterTypes.length == 1) { //获取方法的注解 Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { //获取第一个参数的类型 Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { //获取注解的信息 ThreadMode threadMode = subscribeAnnotation.threadMode(); //对注解的信息进行处理 findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } } } } 复制代码
其实上面代码有注释后还是非常容易读懂的,但是对于这种不经常用的代码在第一次接触时就比较头大,感觉API一点都不熟悉,其实这里每个API都可以根据其名字来判断其作用,最重要的方法就是:
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); 复制代码
通过这个获取注解信息,再获取注解里的参数和方法信息,后面进行处理。
到这里,我们基本上就完成了EventBus中注解是如何生效的分析了。
总结
运行时注解就是在代码运行时进行操作和处理,所以解析注解的操作一般在代码运行到这里才执行,而解析注解就是通过反射来实现,由于反射的API不经常用,所以有一点不熟悉。
也是因为反射,会导致性能有点下降,所以下篇文章我们来看看编译时注解如何使用。
作者:元浩875
链接:https://juejin.cn/post/7072324200685371428