阅读 157

注解全解析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个部分,声明注解和在适当的时候解析注解:

运行时注解.png

声明注解

一想到声明注解,就要熟记那几个元注解,因为元注解是用来修饰自定义注解的,我们看一下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(); 复制代码

上面代码对应方法和结果如下:

Class类型.png

可以点击图片放大观看结果,通过这些方法我们就很容易得到一个类的信息,比如字段、方法等。

其实这么多方法记起来也不难,普通的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


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐