今天讲下注解吧,现在遇到的用注解的开源库越来越多,虽然知道怎么用,但是其原理,怎么写都还不清楚。学习了一些网站和资料,先共享下我的学习资料:
Java Annotation 及几个常用开源项目注解原理简析
安卓注解那些事儿
Android注解支持(Support Annotations)
Java基础加强总结(一)——注解(Annotation)
注解的分类方式有很多:
- 标准的Annotation:override、deprecated、SuppressWarnings等(这些为Java自带)。
- 元Annotation:这些注解是用来修饰注解的,例如Target(说明该注解是用来形容哪些程序元素的,例如Method、Field、Class等)、Retention(指明注解的生命周期,后面会详述)、Documented(是否保存到Javadoc中)、Inherited(能否被继承)。
- 其他Annotation:包括Android已经提供的Annotation(support-annotation包中)和自定义的Annotation。
当然还有其他的分类方式,根据注解的Retention来区分,刚刚说到Retention是注解的生命周期,那分为Source(源码时注解)、Class(编译时注解)、Runtime(运行时注解)。@Retention(RetentionPolicy.SOURCE)这个注解的意思是让注解只在java源文件中存在,编译成.class文件后注解就不存在了。@Retention(RetentionPolicy.CLASS)这个注解的意思是让注解在java源文件(.java文件)中存在,编译成.class文件后注解也还存在,被注解类标识的类被类加载器加载到内存中后MyAnnotation注解就不存在了。如果是Runtime的话,则加载到内存中,该注解还存在,下面是详细解释:
当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时可能会把Java源程序上的一些注解给去掉,java编译器(javac)在处理java源程序时,可能会认为这个注解没有用了,于是就把这个注解去掉了,那么此时在编译好的class中就找不到注解了, 这是编译器编译java源程序时对注解进行处理的第一种可能情况,假设java编译器在把java源程序编译成class时,没有把java源程序中的注解去掉,那么此时在编译好的class中就可以找到注解,当程序使用编译好的class文件时,需要用类加载器把class文件加载到内存中,class文件中的东西不是字节码,class文件里面的东西由类加载器加载到内存中去,类加载器在加载class文件时,会对class文件里面的东西进行处理,如安全检查,处理完以后得到的最终在内存中的二进制的东西才是字节码,类加载器在把class文件加载到内存中时也有转换,转换时是否把class文件中的注解保留下来,这也有说法,所以说一个注解的生命周期有三个阶段:java源文件是一个阶段,class文件是一个阶段,内存中的字节码是一个阶段,javac把java源文件编译成.class文件时,有可能去掉里面的注解,类加载器把.class文件加载到内存时也有可能去掉里面的注解,因此在自定义注解时就可以使用Retention注解指明自定义注解的生命周期,自定义注解的生命周期是在RetentionPolicy.SOURCE阶段(java源文件阶段),还是在RetentionPolicy.CLASS阶段(class文件阶段),或者是在RetentionPolicy.RUNTIME阶段(内存中的字节码运行时阶段),根据JDK提供的API可以知道默认是在RetentionPolicy.CLASS阶段 (JDK的API写到:the retention policy defaults to RetentionPolicy.CLASS.)
如果是Source和Class的话,即使引起Android Studio报错,也不会影响运行,依旧可以运行。举个简单的例子,EventBus库(最新的版本onEvent函数使用了@Subscribe注解),我们知道其原理是当对象注册了EventBus后,EventBus会记录该类onEvent方法,当检测到有消息post出来后,会调用有subscribe注解的方法,试想一下,如果这歌subscribe是source或者class的话,那么加载到内存的时候,就已经没有了,那么Eventbus找不到任何方法来处理事件,所以subscribe一定是运行时,看下代码:
package org.greenrobot.eventbus;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
/**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean sticky() default false;
/** Subscriber priority to influence the order of event delivery.
* Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
* others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
* delivery among subscribers with different {@link ThreadMode}s! */
int priority() default 0;
}
果然没错,验证了我们的猜想。
安卓中的注解分为八大类:
- Nullness注解:@Nullable、@NoNull(为方法的形参加上这个,可以减少判空的代码)
- 资源类型注解:以Res结尾,例如@StringRes、@ColorRes、@IdRes等
- 权限注解:@requestPermission,这个注解在PermissionDispatcher这个开源库中用到
- CallSuper注解:被@CallSuper修饰的方法,一定要调用super方法
- 枚举注解:@IntDef和@StringDef,具体用法可以看Toast下Duration
- 线程注解:@MainThread、@WorkThread等,被这些注解修饰的方法只能在该线程内调用
- 变量限制注解:@Size、@IntRange、@FloatRange等
- 结果检查注解:@CheckResult,被该注解修饰的方法,需要对方法的返回值进行处理。例如Context.checkPermission(@NonNull String permission, int pid, int uid)方法,防止别人误解该方法就已经算是请求权限了,调用该方法一定要判断返回值,权限是否被赋予,如果不判断,调用该方法无用。如果只是简单调该函数,并未判断返回值,则会提示是否使用另外一个函数。
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
@PackageManager.PermissionResult
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
怎么写一个注解可以参照上面共享文章中的最后一篇:Java基础加强总结(一)——注解(Annotation)
讲到现在都还没举一个具体的例子,那就让我们来看看常见的ColorRes:
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface ColorRes {
}
解释下上述代码:该注解将会保存到Javadoc中;该注解是编译时注解,在class文件中还会存在,但是在内存中就没了;该注解用来修饰方法、参数、属性、局部变量。
还有一些比较好用的注解,例如@Keep,该注解表示被该注解修饰的元素将不会被混淆。
下面讲下编译时解析:由apt(Annotation Processing Tool)自动解析
- 自定义类继承自AbstractProcessor
- 重写该类的Process函数
apt在编译时自动查找所有继承自AbstractProcessor的类,调用process函数去处理。