安卓注解使用介绍

1,710 阅读6分钟

概述

在Java中,注解(Annotation)引入始于Java5,用来描述Java代码的元信息,通常情况下注解不会直接影响代码的执行,尽管有些注解可以用来做到影响代码执行。

在代码文件中使用‘@’字符告诉编译器接下来的是一个注解。注解可以用在类,构造方法,成员变量,方法,参数等的声明中。作用主要是对编译器警告等辅助工具产生影响,如果传递了错误的类型那么编译器就会发出警告,这样就可以在编码和维护的过程中辅助发现问题,提高开发效率,提升代码质量,也促进形成编码规范。

安卓开发中用到的注解主要有四个方面:JDK内置注解、JDK自定义注解包、android sdk内置注解、android.support.annotation注解。

JDK内置注解

如下图,左侧是JDK提供的三个标准注解,在java.lang包内。右侧JDK提供的四个是对自定义注解的支持,在java.lang.annotation包内。

  • @Deprecated 是一个标记注解,表示被标记的成员变量或者成员方法已经不建议使用,原因可能是这个方法/变量有缺陷或者在新的SDK中已经不被支持。

  • @Override 注解在继承过程中,标识对父类方法的覆盖关系。这个在Java中不是必须的,但是建议在需要的地方强制使用。防止在子类或者父类中误操作修改方法签名或者遗漏相关的代码(kotlin中对于子类覆盖父类方法强制使用override关键字,不再需要注解标记)。

  • @SuppressWarnings 用来抑制编译器生成警告信息,对指定类型的警告保持静默。可以修饰类、方法、方法参数、属性和局部变量,采用就近原则,尽量放在被需要静默的警告语句附近。接收一个字符串或者一个字符集作为参数(参数详细介绍参考文章),它指示将取消的警告。

  • JDK提供了四种元注解,支持用户对注解进行自定义扩展。自定义注解后面会详细介绍,这里先略过。

Android SDK内置注解

Android SDK注解有两个@SuppressLint和@TargetApi。

这两个注解是使用Lint静态检测对应的标记。如果禁用了Lint,用或者不用这些注解都没有太大关系。

@TargetApi:Android工程需要设置所支持的最小的系统版本,Android Studio是在Gradle中设置minSdkVersion的值。如果某个方法或者类被声明需要在某个版本和更高版本的系统上运行,可以使用@RequiresApi(requires)声明支持的最小系统版本。当在声明minSdkVersion的工程中使用了requires大于minSdkVersion的类或者方法时,Lint就会报错误提醒,这时候可以使用@TargetApi使Lint保持静默,但是要添加代码为低版本的系统提供对应的备选方案,否则在低版本系统上运行会产生崩溃。

@SuppressLint:上面的@TargetApi注解只针对API版本进行注解,使Lint对版本错误保持静默。SuppressLint注解针对的范围更广,通过设置一个参数(identified by the lint issue id)通知Lint对相应的警告⚠和错误❎️保持静默。

@SuppressLint("NewApi"),这个注解可以实现@TargetApi(version)相同的作用,只是没有指定特定的API版本,导致工程师和Lint都不知道响应的范围,容易导致错误,不建议使用, 对于Lint的警告,建议做出相应的更改以符合设定的Lint规则,对一些代码风格类型的检查可以适当放宽条件。

Android Support注解

下面图中给出Android Support包中的注解,现在已经迁移到androidx,使用时对应上就好。

Android Support Library提供com.android.support:support-annotations对Android的注解进行了拓展。下图以v25为例列出了所有定义的46个注解(到v27增加了5个,分别为ColorLong、FontRes、GuardedBy、HalfFloat、NavigationRes)。

资源类型注解

资源类注解有22个,用于规范函数返回值、函数参数、类成员和局部变量,如下:

取值范围类型注解

取值范围类3个注解,IntRange/FloatRange对响应类型的变量或参数规定取值范围,参数有from和to两个;Size对数组的长度约束,参数min/max(含)组合声明数组的长度范围,参数value指定数据的具体长度,参数multiple指定数组长度必须是某个数字的倍数。

枚举定义注解

Android中引入IntDef和StringDef,建议使用枚举注解代替枚举类型。

使用 Enum 的缺点:每一个枚举值都是一个对象,在使用它时会增加额外的内存消耗,所以枚举相比于Integer和String会占用更多的内存,较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的开销,使我们的应用需要更多的空间。特别是分dex的大APP,枚举的初始化很容易导致ANR。

它们两个是注解的注解,作用在自定义的注解接口上,规定新注解的取值范围。

@Retention(SOURCE)
@StringDef({
    POWER_SERVICE,
    WINDOW_SERVICE,
    LAYOUT_INFLATER_SERVICE
})
public @interface ServiceName {}
public static final String POWER_SERVICE = "power";
public static final String WINDOW_SERVICE = "window";
public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
@Retention(SOURCE)
@IntDef({
    NAVIGATION_MODE_STANDARD, 
    NAVIGATION_MODE_LIST, 
    NAVIGATION_MODE_TABS
})
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
  ...
public abstract void setNavigationMode(@NavigationMode int mode);
@NavigationMode
public abstract int getNavigationMode();

自定义注解

上面的枚举注解,就是自定义注解的例子。自定义注解使用关键字@interface声明,然后用相应的属性修饰。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Entity {
    String value();
    String name();
}
  • @Documented 表示拥有该注解的元素可通过javadoc此类的工具进行文档化。该类型应用于注解那些影响客户使用带注释(comment)的元素声明的类型。如果类型声明是用Documented来注解的,这种类型的注解被作为被标注的程序成员的公共API。
  • @Inherited 注解在类型继承关系中的表现参考文章:java @Inherited注解的作用
  • @Retention 表示该注解类型的注解保留的时长。可用的参数都在枚举类型RetentionPolicy中给出了定义。当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。
  • @Target:表示该注解类型的所使用的程序元素类型。可用的参数都在枚举类型ElementType中给出了定义。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。

附录:ButterKnife

使用ButterKnife只需要在module的gradle文件中加入下面代码:

implementation 'com.jakewharton:butterknife:8.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'

在library工程中,直接使用R.xxx.xxx会报“元素值必须为常量表达式”的错误提示,处理步骤如下:

  • 在根gradle文件中添加(版本根据需要变化):
classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
  • 在module的gradle中添加:
apply plugin: 'com.jakewharton.butterknife'
  • R.xxx.xxx替换成R2.xxx.xxx

参考文章