一、什么是注解
注解是元数据的一种形式,它提供有关程序的数据,该数据不属于程序本身。注解对其注释的代码操作没有直接影响。换句话说,注解携带元数据,并且会引入一些和元数据相关的操作,但不会影响被注解的代码的逻辑
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java™ Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
...
}
Java中所有的注解都扩展自 Annotation
这个接口,注解本质就是一个接口。比如我们最常见的重写@Override
,它没有参数,所以是一个标记注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
1.1 元注解
以@Override
为例,我们发现上面有两个注解@Target
和@Retention
,像这种注解的注解被称为元注解。Java中的元注解有以下几个,
@Target
这个注解标识了被修饰注解的作用对象,看源码,
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
value
是一个数组,说明该注解的作用对象可以是多个,取值对象在ElementType中,
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE
}
不同的值代表被注解可修饰的范围,例如TYPE只能修饰类、接口和枚举定义,METHOD只能修饰方法,比如@Override只能注解方法。这其中有个很特殊的值叫做 ANNOTATION_TYPE, 是专门表示元注解的。
@Retention
该注解指定了被修饰的注解的生命周期。定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
value
返回了一个RetentionPolicy
枚举类型,
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
- SOURCE:表示注解编译时可见(在原文件有效),编译完后就被丢弃。这种注解一般用于在编译期做一些事情,比如可以让注解处理器生成一些代码,或者是让注解处理器做一些额外的类型检查,等等;
- CLASS:表示在编译完后写入 class 文件(在class文件有效),但在类加载后被丢弃。这种注解一般用于在类加载阶段做一些事情,比如Android的资源类型检查(@ColorRes、@DrawableRes、@Px);
- RUNTIME:表示注解会一直起作用。
@Override
就是编译时可见,编译成class之后丢失。
@Documented
编译器在生成Java文档时将被注解的元素包含进去。这意味着使用了@Documented
注解的注解,其注解的信息会被包含在生成的文档中,方便开发者查阅。@Documented
是一个标记注解,没有成员。
@Inherited
用于指示子类是否继承父类的注解。当一个注解被标注为 @Inherited
时,如果一个类使用了被 @Inherited
标注的注解,那么其子类也会自动继承这个注解。这在一些框架或库中很有用,可以自动继承某些特性或行为。需要注意的是,@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation
一个注解可以有多个target(ElementType),但只能有一个retention(RetentionPolicy)。
1.2 自定义注解
使用@interface
标识CustomeAnnotation
是一个注解,表示扩展自java.lang.annotation.Annotation
public @interface CustomeAnnotation {}
然后需要指定注解的作用域与生命周期,使用元注解@Target
和@Retention
来注解CustomeAnnotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomeAnnotation {}
上面表示CustomeAnnotation
注解只能作用于方法上,并且其所携带的元数据会保留到运行时。CustomeAnnotation
没有携带元数据,意义不大,下面给注解添加参数(元数据)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomeAnnotation {
String value() default "";
}
}
注解中每一个方法就是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型。使用default
定义参数默认值。
注解参数的可支持数据类型:
- 所有基本数据类型(int, float, boolean, byte, double, char, long, short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用public或默认(default)这两个访问权修饰。上面例子把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String,Enum,Class,Annotations等数据类型,以及这一些类型的数组。例如,上面例子中参数名就是value
,参数类型就是String。
第三,如果只有一个参数成员,最好把参数名称设为 value
,后加小括号。
使用的时候通过value = <你的元数据>
传递参数,如果只有一个参数,可以不写参数名,如果有多个,则需要显示声明参数名。
二 Android常见注解
2.1 Nullness注解
- @NonNull
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}
- @Nullable
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface Nullable {
}
从名字上就可以很明显的看出,@NonNull表示这个参数、变量等不能为空,而@Nullable则表示可以为空,举个例子:
private void test(@NonNull String test) {
}
如果我们有这样的一个函数,用@NonNull注解表示参数不能为空,如果我们这样调用这个函数
test(null);
我们会得到这样的警告提示,告诉我们这个参数被注解为@NonNull
如果我们将这个函数改为:
private void testNonNull(@Nullable String test) {
}
或者没有任何注解时,就没有提示了。
2.2 资源类型注解
所有以“Res”结尾的注解,都是资源类型注解。大概包括:@AnimatorRes、@AnimRes、@AnyRes、@ArrayRes、@AttrRes、@BoolRes、@ColorRes、@DimenRes、@DrawableRes、@FractionRes、@IdRes、@IntRes、@IntegerRes、@InterpolatorRes、@LayoutRes、@MenuRes、@PluralsRes、@RawRes、@StringRes、@StyleableRes、@StyleRes、@TransitionRes、@XmlRes
使用方法也都是类似的,这里举个例子:
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}
private String getStringById(@StringRes int stringId) {
return getResources().getString(stringId);
}
如果我们这样写String name = getStringById(R.string.app_name);
是不会有问题的,但是团队中的其他小伙伴在调用的时候写错了怎么办?比如String name = getStringById(R.layout.activity_main);
如果@StringRes注解,我们看不到任何的提醒,而运行时就会报错,但是如果加上了@StringRes注解,我们就可以看到这样的错误提示:
2.3 线程注解
包括@AnyThread、@UiThread和@WorkerThread,表明需要运行在什么线程上。
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface WorkerThread {
}
例如有一个函数,需要做一些耗时操作,我们希望它不要运行在主线程上
@WorkerThread
private void testThread() {
// 耗时操作
}
如果我们在主线程中调用这个函数会怎么样呢?
而如果这样调用就不会有问题,这样就保证了我们这个耗时操作不会执行在主线程中。
new Thread(new Runnable() {
public void run() {
testThread();
}
}).start();
2.4 变量限制注解
变量限制注解主要包含@IntRange和@FloatRange两种,使用方法类似,都是限制了范围,这里以@IntRange为例。
@Retention(CLASS)
@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface IntRange {
/** Smallest value, inclusive */
long from() default Long.MIN_VALUE;
/** Largest value, inclusive */
long to() default Long.MAX_VALUE;
}
源码中可以看到,这里包含了from()
和to()
,默认值分别是Long的最小值Long.MIN_VALUE
和Long的最大值Long.MAX_VALUE
。
例如我们有个方法,需要限制输入的范围,我可以这样写:
private void testRange(@IntRange(from = 1, to = 10) int number) {
}
如果调用者输入了一个超出范围的值时,会这样提示他。
2.5 权限注解
如果我们有方法需要使用某种权限,可以加上@RequiresPermission这个注解。
@RequiresPermission(Manifest.permission.CALL_PHONE)
private void testPermission() {
}
如这里需要打电话的权限,但是我并没有在应用中加入该权限。
没有错,AS就是这么强大,会告诉我们这个权限可能会被用户拒绝,所以我们应该在代码中对这个权限进行检查。
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
testPermission();
这样就没有问题了。
2.6 结果检查注解
如果我们写了一个有返回值的函数,并且我们希望调用者对这个返回值进行使用或者检查。这个时候@CheckResult注解就派上用场了。
@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CheckResult {
/** Defines the name of the suggested method to use instead, if applicable (using
* the same signature format as javadoc.) If there is more than one possibility,
* list them all separated by commas.
* <p>
* For example, ProcessBuilder has a method named {@code redirectErrorStream()}
* which sounds like it might redirect the error stream. It does not. It's just
* a getter which returns whether the process builder will redirect the error stream,
* and to actually set it, you must call {@code redirectErrorStream(boolean)}.
* In that case, the method should be defined like this:
* <pre>
* @CheckResult(suggest="#redirectErrorStream(boolean)")
* public boolean redirectErrorStream() { ... }
* </pre>
*/
String suggest() default "";
}
比如有这样一个方法,返回了String。
@CheckResult
private boolean testCheckResult() {
return true;
}
如果我们不关心他的返回值。
提示我们结果没有被使用。如果改为boolean result = testCheckResult();
就不会有问题了。@CheckResult注解保证了我们方法的返回值一定会得到使用。
2.7 CallSuper注解
如果我们有一个父类Father,有一个方法display(),有一个子类Child继承了Father,并重写了display()方法,并不会有任何问题。
class Father {
public void display() {
Log.i(TAG, "display: Father");
}
}
class Child extends Father {
@Override
public void display() {
Log.i(TAG, "display: Child");
}
}
但是,如果我想让子类Child在调用display()方式也将父类Father的某些信息一起打印出来怎么办?没错,在子类的display()方法中调用super.display();
即可。那么,我们怎么保证子类就一定会调用super的方法呢?@CallSuper注解登场。
@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CallSuper {
}
@CallSuper注解表示重写的方法必须调用父类的方法,注意,这里的Target只有METHOD,并没有CONSTRUCTOR,所以构造函数是不能使用这个注解的。
还是刚才的例子,如果我们在父类的方法上加上@CallSuper注解,这个时候子类中重写的方法就会提示这样的错误。
这样就提醒我们需要加上super的方法。
2.8 枚举注解
Android官方强烈建议不要在Android程序里面使用到enum,官方的Training课程里面有下面这样一句话:Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
既然官方都这样说了,那就尽量不要使用枚举了,可是不使用枚举使用什么呢?没错,静态常量。
举个例子,比如我自己写了一个提示框,需要提示用户一些信息,所以这样写:
public class MyTip {
public static void show(String message) {
// 显示提示框
}
}
我希望这个提示框在显示一定时间后自动关掉,所以定义了两个静态常量,一个长时间一个短时间,并且作为show方法的一个参数。
public class MyTip {
public static final int LONG_TIME = 0;
public static final int SHORT_TIME = 1;
public static void show(String message, int type) {
// 显示提示框
}
}
我可以这样让我的提示框显示一个较长的时间。
MyTip.show("message", MyTip.LONG_TIME);
MyTip.show("message", MyTip.LONG_TIME);
但是有个问题,这里我传入的参数是MyTip.LONG_TIME
,但是实际上不管传入的是1还是0,甚至是MyTip.show("message", 2);
都不会提示错误,因为只要是int就可以,这显示不是我想要的。这里我们就需要用到枚举注解了,@IntDef和@StringDef
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
long[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
}
这时候我再修改一下代码
public class MyTip {
public static final int LONG_TIME = 0;
public static final int SHORT_TIME = 1;
@IntDef({LONG_TIME, SHORT_TIME})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
public static void show(String message, @Type int type) {
// 显示提示框
}
}
这里自己写了一个注解@Type,并且使用了@IntDef注解,value就是两个静态常量。这时候再看调用的地方。
是不是非常熟悉?没错,我们熟悉的Toast就是用@IntDef注解这么实现的。
@IntDef(prefix = { "LENGTH_" }, value = {
LENGTH_SHORT,
LENGTH_LONG
})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
/**
* Set how long to show the view for.
* @see #LENGTH_SHORT
* @see #LENGTH_LONG
*/
public void setDuration(@Duration int duration) {
mDuration = duration;
mTN.mDuration = duration;
}
/**
* Return the duration.
* @see #setDuration
*/
@Duration
public int getDuration() {
return mDuration;
}
三 注解在安卓源码中的使用
3.1 注解在Lifecycle源码中的使用
Jetpack生命周期管理 -Lifecycle实战及源码分析
class ClassesInfoCache {
private final Map<Class, CallbackInfo> mCallbackMap = new HashMap<>();//所有观察者的回调信息
private final Map<Class, Boolean> mHasLifecycleMethods = new HashMap<>();//观察者是否有注解了生命周期的方法
CallbackInfo getInfo(Class<?> klass) {
CallbackInfo existing = mCallbackMap.get(klass);//如果已经存在当前观察者回调信息 直接取
if (existing != null) {
return existing;
}
existing = createInfo(klass, null);//没有就去收集信息并创建
return existing;
}
private CallbackInfo createInfo(Class<?> klass, @Nullable Method[] declaredMethods) {
Class<?> superclass = klass.getSuperclass();
Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();//生命周期事件到来 对应的方法
...
Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);//反射获取观察者的方法
boolean hasLifecycleMethods = false;
for (Method method : methods) {//遍历方法 找到注解OnLifecycleEvent
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation == null) {
continue; //没有注解OnLifecycleEvent 就return
}
hasLifecycleMethods = true;//有注解OnLifecycleEvent
Class<?>[] params = method.getParameterTypes(); //获取方法参数
int callType = CALL_TYPE_NO_ARG;
if (params.length > 0) { //有参数
callType = CALL_TYPE_PROVIDER;
if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
throw new IllegalArgumentException(//第一个参数必须是LifecycleOwner
"invalid parameter type. Must be one and instanceof LifecycleOwner");
}
}
Lifecycle.Event event = annotation.value();
if (params.length > 1) {
callType = CALL_TYPE_PROVIDER_WITH_EVENT;
if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
throw new IllegalArgumentException(//第二个参数必须是Event
"invalid parameter type. second arg must be an event");
}
if (event != Lifecycle.Event.ON_ANY) {
throw new IllegalArgumentException(//有两个参数 注解值只能是ON_ANY
"Second arg is supported only for ON_ANY value");
}
}
if (params.length > 2) { //参数不能超过两个
throw new IllegalArgumentException("cannot have more than 2 params");
}
MethodReference methodReference = new MethodReference(callType, method);
verifyAndPutHandler(handlerToEvent, methodReference, event, klass);//校验方法并加入到map handlerToEvent 中
}
CallbackInfo info = new CallbackInfo(handlerToEvent);//获取的 所有注解生命周期的方法handlerToEvent,构造回调信息实例
mCallbackMap.put(klass, info);//把当前观察者的回调信息存到ClassesInfoCache中
mHasLifecycleMethods.put(klass, hasLifecycleMethods);//记录 观察者是否有注解了生命周期的方法
return info;
}
static class MethodReference {
final int mCallType;
final Method mMethod;
MethodReference(int callType, Method method) {
mCallType = callType;
mMethod = method;
mMethod.setAccessible(true);
}
void invokeCallback(LifecycleOwner source, Lifecycle.Event event, Object target) {
//noinspection TryWithIdenticalCatches
try {
switch (mCallType) {
case CALL_TYPE_NO_ARG:
mMethod.invoke(target);
break;
case CALL_TYPE_PROVIDER:
mMethod.invoke(target, source);
break;
case CALL_TYPE_PROVIDER_WITH_EVENT:
mMethod.invoke(target, source, event);
break;
}
} catch (InvocationTargetException e) {
throw new RuntimeException("Failed to call observer method", e.getCause());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation == null) {
continue; //没有注解OnLifecycleEvent 就return
}
我们看下 OnLifecycleEvent注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OnLifecycleEvent {
Lifecycle.Event value();
}
public static enum Event {
ON_CREATE,
ON_START,
ON_RESUME,
ON_PAUSE,
ON_STOP,
ON_DESTROY,
ON_ANY;
private Event() {
}
}
value返回Event枚举类型
//Presenter
class MyPresenter implements LifecycleObserver {
private static final String TAG = "Lifecycle_Test";
private final IView mView;
public MyPresenter(IView view) {mView = view;}
@OnLifecycleEvent(value = Lifecycle.Event.ON_START)
private void getDataOnStart(LifecycleOwner owner){
Log.i(TAG, "getDataOnStart: ");
Util.checkUserStatus(result -> {
//checkUserStatus是耗时操作,回调后检查当前生命周期状态
if (owner.getLifecycle().getCurrentState().isAtLeast(STARTED)) {
start();
mView.showView();
}
});
}
@OnLifecycleEvent(value = Lifecycle.Event.ON_STOP)
private void hideDataOnStop(){
Log.i(TAG, "hideDataOnStop: ");
stop();
mView.hideView();
}
}