前言
上一篇文章我们介绍了一些注解的基础知识,必须要记住,从这篇开始我们就来看看一些常见的库是如何使用注解的。首先看一下运行时注解的使用,我们这里使用EventBus这个库来做示例。
本系列文章:
正文
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不经常用,所以有一点不熟悉。
也是因为反射,会导致性能有点下降,所以下篇文章我们来看看编译时注解如何使用。