注解全解析2 -- 分析EventBus中的运行时注解

1,517 阅读6分钟

前言

上一篇文章我们介绍了一些注解的基础知识,必须要记住,从这篇开始我们就来看看一些常见的库是如何使用注解的。首先看一下运行时注解的使用,我们这里使用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不经常用,所以有一点不熟悉。

也是因为反射,会导致性能有点下降,所以下篇文章我们来看看编译时注解如何使用。