前言
关于注解我相信大家都见的非常多了, 不论是Android或者Java自带的@Override等注解还是我们经常使用的一些开源库都包含注解,原因很简单,注解可以让代码更简洁,比如Retrofit、PermissionsDispatcher等库,但是自己定义注解确干的非常少。
所以这篇文章我们就来深挖以下自定义注解的使用,以及拿一个开源库来解析源码,剖析原理。
正文
先来了解一些关于注解的基本知识。
元注解
元注解是在定义自定义注解时需要用到,也就是注解的注解,比如我们自定义的注解需要用在什么地方、这个注解存活的时间等属性都是通过元注解来进行说明。
直接看下面总结
第一眼看时这东西未免有点多,难以记住,大可不必都记着,首先主要用到的就是@Target和@Retention这2个注解。
其中你想你的注解运用在什么地方,就使用@Target来说明;对于具体情况再区分使用运行时注解还是编译时注解,其中RUNTIME就是运行时注解,CLASS范围就是编译时注解,这2种注解区别很大,具体实现方式和侧重点也不一样,主要是下面几点:
哦,看到这里我们就大概懂了,一些开源库有时需要在添加注解后进行build才能使用一些生成的方法,这种就是编译时注解,通过生成新的文件来完成。
所以在考虑自定义什么类型注解是,也是从上面的区别来考虑,比如我需要在运行时拿到什么属性或者资源就使用运行时注解,比如我需要更快的性能,在编译器通过生成文件、使用生成文件的方法来完成需求则使用运行时注解。
接下来我们来看看这2种注解的使用,这里还是以解析库的原码来进行。
运行时注解
这里就介绍一个非常有名的运行时注解库的实现,就是EventBus,关于EventBus的使用这里就不过多解释了,具体查看项目地址:github.com/greenrobot/…
使用很方便在Android就是用于组件之间的通信,
在一个地方调用post方法,然后注册订阅者便可以收到消息,其中的接收方法是使用注解来完成的,比如下面代码:
@Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0)
public void onEventMainThread(TestFinishedEvent event) {
Test test = event.test;
String text = "<b>" + test.getDisplayName() + "</b><br/>" + //
test.getPrimaryResultMicros() + " micro seconds<br/>" + //
((int) test.getPrimaryResultRate()) + "/s<br/>";
if (test.getOtherTestResults() != null) {
text += test.getOtherTestResults();
}
text += "<br/>----------------<br/>";
textViewResult.append(Html.fromHtml(text));
}
复制代码
这里的onEventMainThread方法当有post这个类型的事件时便会收到,在这里我们先不讨论eventBus是如何进行消息发布和订阅的,主要来看一下这个@Subscribe这个注解。
根据前一节说的注解类型,运行时注解一般是通过反射来完成,那么这里就有了下面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;
}
复制代码
从上面的元注解我们可以得知这个注解必须要使用在方法上,而且在运行时还保留。
同时注解的参数有3个,这里就要注意了,我们使用注解一般就是为了方便,让代码简洁,所以这里的参数一般不要太多,在使用时直接用逗号隔开即可:
@Subscribe(threadMode = ThreadMode.MAIN, sticky = false, priority = 0)
复制代码
那既然在一个类中我已经定义了订阅者,那啥时候来把这个订阅者添加到EventBus系统中呢,看一下官方使用文档:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
复制代码
这里直接在组件的生命周期开始和结束进行register,然后在这个组件里的订阅者便被添加到系统中,所以这里解析注解的步骤应该就是register方法中进行。
解析注解
直接看一下register()的代码:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
复制代码
我相信很多人看到这个getClass()方法获取的Class对象都有点惧怕,因为平时接触这玩意太少了,感觉这部分代码很难看懂,其实把它扒开了分析一波就不难了。
根据前面的思路,运行时注解就是找到注解,找到其参数,然后再干事,这里找到注解和参数很关键,其中就是这个Class类型,下面先说一下这个。
Class类型
直接看代码,比如这里的Activity,代码是:
//逻辑不用看,看有哪些方法
public class TestRunnerActivity extends Activity {
private TestRunner testRunner;
private EventBus controlBus;
private TextView textViewResult;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_runtests);
textViewResult = findViewById(R.id.textViewResult);
controlBus = new EventBus();
controlBus.register(this);
}
@Override
protected void onResume() {
super.onResume();
if (testRunner == null) {
TestParams testParams = (TestParams) getIntent().getSerializableExtra("params");
testRunner = new TestRunner(getApplicationContext(), testParams, controlBus);
if (testParams.getTestNumber() == 1) {
textViewResult.append("Events: " + testParams.getEventCount() + "\n");
}
textViewResult.append("Subscribers: " + testParams.getSubscriberCount() + "\n\n");
testRunner.start();
}
}
@Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0)
public void onEventMainThread(TestFinishedEvent event) {
Test test = event.test;
String text = "<b>" + test.getDisplayName() + "</b><br/>" + //
test.getPrimaryResultMicros() + " micro seconds<br/>" + //
((int) test.getPrimaryResultRate()) + "/s<br/>";
if (test.getOtherTestResults() != null) {
text += test.getOtherTestResults();
}
text += "<br/>----------------<br/>";
textViewResult.append(Html.fromHtml(text));
if (event.isLastEvent) {
findViewById(R.id.buttonCancel).setVisibility(View.GONE);
findViewById(R.id.textViewTestRunning).setVisibility(View.GONE);
findViewById(R.id.buttonKillProcess).setVisibility(View.VISIBLE);
}
}
public void onClickCancel(View view) {
// Cancel asap
if (testRunner != null) {
testRunner.cancel();
testRunner = null;
}
finish();
}
public void onClickKillProcess(View view) {
Process.killProcess(Process.myPid());
}
public void onDestroy() {
if (testRunner != null) {
testRunner.cancel();
}
controlBus.unregister(this);
super.onDestroy();
}
}
复制代码
这里面的逻辑不用看,主要看这个类有哪些方法和字段、参数即可,那接下来就是获取这个类的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();
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不经常用,但是看过理解一遍后还是很容易的,下一篇我们将通过Android的动态权限库PermissionsDispatcher来剖析编译时注解,包括使用、生成文件等,不容错过,喜欢的点个赞或者关注吧。