阅读 473

Android 注解知多少

注解的概念

什么是注解?

注解又称为标注,用于为代码提供元数据。 作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。可以作用在类、方法、变量、参数和包等上。 你可以通俗的理解成“标签”,这个标签可以标记类、方法、变量、参数和包。

什么用处?

  1. 生成文档;
  2. 标识代码,便于查看;
  3. 格式检查(编译时);
  4. 注解处理(编译期生成代码、xml文件等;运行期反射解析;常用于三方框架)。

分类

  1. 元注解

元注解是用于定义注解的注解,元注解也是 Java 自带的标准注解,只不过用于修饰注解,比较特殊。 2. 内置的标准注解 就是用在代码上的注解,不同的语言或环境提供有不同的注解(Java Kotlin Android)。使用这些注解后编译器就会进行检查。 3. 自定义注解 用户可以根据自己的需求定义注解。

标准的注解讲解

Java 的标准注解

元注解在后面讲解自定义注解时再一起介绍,这里只先介绍标准注解。

名称描述
@Override检查该方法是否正确地重写了父类的方法。如果重写错误,会报编译错误;
@Deprecated标记过时方法。如果使用该方法,会报编译警告;
@SuppressWarnings指示编译器去忽略注解中声明的警告;
@SafeVarargs忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告;(Java 7 开始支持)
@FunctionalInterface标识一个匿名函数或函数式接口(Java 8 开始支持)

Android 注解库

support.annotation 是 Android 提供的注解库,与 Android Studio 内置的代码检查工具配合,注解可以帮助检测可能发生的问题,例如 null 指针异常和资源类型冲突等。

使用前配置 在 Module 的 build.gradle 中添加配置:

implementation 'com.android.support:support-annotations:版本号'
复制代码

注意:如果您使用 appcompat 库,则无需添加 support-annotations 依赖项。因为 appcompat 库已经依赖注解库。(一般创建项目时已自动导入)

Null 性注解

  • @Nullable 可以为 null
  • @NonNull 不可为 null

用于给定变量、参数或返回值是否可以为 null 。

import android.support.annotation.NonNull;
...
    @NonNull // 检查 onCreateView() 方法本身是否会返回 null。
    @Override
    public View onCreateView(String name, @NonNull Context context,
      @NonNull AttributeSet attrs) {
      ...
      }
...
复制代码

资源注解

验证资源类型时非常有用,因为 Android 对资源的引用以整型形式传递。如果代码需要一个参数来引用特定类型的资源,可以为该代码传递预期的引用类型 int,但它实际上会引用其他类型的资源,如 R.string.xxx 资源。

Android 中的资源类型有很多,Android 注解为每种资源类型都提供了相对应的注解。

  • AnimatorRes //动画资源(一般为属性动画)
  • AnimRes //动画资源(一般为视图动画)
  • AnyRes //任何类型的资源引用,int 格式
  • ArrayRes //数组资源 e.g. android.R.array.phoneTypes
  • AttrRes //属性资源 e.g. android.R.attr.action
  • BoolRes //布尔资源
  • ColorRes //颜色资源
  • DimenRes //尺寸资源
  • DrawableRes //可绘制资源
  • FontRes //字体资源
  • FractionRes //百分比数字资源
  • IdRes //Id 引用
  • IntegerRes //任意整数类型资源引用
  • InterpolatorRes //插值器资源 e.g. android.R.interpolator.cycle
  • LayoutRes //布局资源
  • MenuRes //菜单资源
  • NavigationRes //导航资源
  • PluralsRes //字符串集合资源
  • RawRes //Raw 资源
  • StringRes //字符串资源
  • StyleableRes //样式资源
  • StyleRes //样式资源
  • TransitionRes //转场动画资源
  • XmlRes //xml 资源
  • 使用 @AnyRes 可以指明添加了此类注解的参数可以是任何类型的 R 资源。
  • 尽管可以使用 @ColorRes 指定某个参数应为颜色资源,但系统不会将颜色整数(采用 RRGGBB 或 AARRGGBB 格式)识别为颜色资源。您可以改用 @ColorInt 注解来指明某个参数必须为颜色整数。

线程注解

线程注解可以检查某个方法是否从特定类型的线程调用。支持以下线程注解:

  • @MainThread
  • @UiThread
  • @WorkerThread
  • @BinderThread
  • @AnyThread

注意:构建工具会将 @MainThread@UiThread 注解视为可互换,因此您可以从 @MainThread 方法调用 @UiThread 方法,反之亦然。不过,如果系统应用有多个视图在不同的线程上,那么界面线程可能会与主线程不同。因此,您应使用 @UiThread 为与应用的视图层次结构关联的方法添加注解,并使用 @MainThread 仅为与应用生命周期关联的方法添加注解。

如果某个类中的所有方法具有相同的线程要求,您可以为该类添加一个线程注解,以验证该类中的所有方法是否从同一类型的线程调用。

值约束注解

值约束注解可以验证所传递参数的值是否在指定范围内:

  • @IntRange
  • @FloatRange
  • @Size

@IntRange@FloatRange 在应用到用户可能会弄错范围的参数时最为有用。

// 确保 alpha 参数是包含 0 到 255 之间的整数值
public void setAlpha(@IntRange(from=0,to=255) int alpha) { ... }

// 确保 alpha 参数是包含 0.0 到 1.0 之间的浮点值
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
复制代码

@Size 注解可以检查集合或数组的大小,以及字符串的长度。@Size 注解可用于验证以下特性:

  • 最小大小(例如 @Size(min=2)
  • 最大大小(例如 @Size(max=2)
  • 确切大小(例如 @Size(2)
  • 大小必须是指定数字的倍数(例如 @Size(multiple=2)

例如,@Size(min=1) 可以检查某个集合是否不为空,@Size(3) 可以验证某个数组是否正好包含三个值。

// 确保 location 数组至少包含一个元素
void getLocation(View button, @Size(min=1) int[] location) {
    button.getLocationOnScreen(location);
}
复制代码

权限注解

使用 @RequiresPermission 注解可以验证方法调用方的权限。要检查有效权限列表中是否存在某个权限,请使用 anyOf 属性。要检查是否具有某组权限,请使用 allOf 属性。

// 以确保 setWallpaper() 方法调用方具有 permission.SET_WALLPAPERS 权限
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
复制代码
// 要求 copyImageFile() 方法的调用方具有对外部存储空间的读取权限,以及对复制的映像中的位置元数据的读取权限
@RequiresPermission(allOf = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_MEDIA_LOCATION})
public static final void copyImageFile(String dest, String source) {
    //...
}

复制代码

对于 intent 的权限,请在用来定义 intent 操作名称的字符串字段上添加权限要求:

@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
            "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
复制代码

如果您需要对内容提供程序拥有单独的读取和写入访问权限,则需要将每个权限要求封装在 @RequiresPermission.Read@RequiresPermission.Write 注解中:

@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
复制代码

返回值注解

使用 @CheckResult 注解可检查是否对方法的返回值进行处理,验证是否实际使用了方法的结果或返回值。

这个可能比较难理解,这里借助 Java String.trim() 举个例子解释一下(通过例子应该能够很直观的理解了,不需要过多解释了):

String str = new String("    www.ocnyang.com    ");
// 删除头尾空白
System.out.println("网站:" + str.trim() + "。");//打印结果:网站:www.ocnyang.com。
System.out.println("网站:" + str + "。");//打印结果:网站:    www.ocnyang.com    。
复制代码

以下示例为 checkPermissions() 方法添加了注解,以确保会实际引用该方法的返回值。此外,这还会将 enforcePermission() 方法指定为要向开发者建议的替代方法:

@CheckResult(suggest="#enforcePermission(String,int,int,String)")
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
复制代码

CallSuper 注解

@CallSuper 注解主要是用来强调在覆盖父类方法的时候,在实现父类的方法时及时调用对应的 super.xxx() 方法,当使用 @CallSuper 修饰了某个方法,如果子类覆盖父类该方法后没有实现对父类方法的调用就会报错。

Keep 注解

使用 @Keep 注解可以确保在构建混淆缩减代码大小时,不会移除带有该注解的类或方法。 该注解通常添加到通过反射访问的方法和类,以防止编译器将代码视为未使用。

注意:使用 @Keep 添加注解的类和方法会始终包含在应用的 APK 中,即使您从未在应用逻辑中引用这些类和方法也是如此。

代码公开范围注解(了解)

单元测试中可能要访问到一些不可见的类、函数或者变量,这时可以使用@VisibleForTesting 注解来对其可见。

Typedef 注解

枚举 Enum 在 Java 中是一个完整的类。而枚举中的每一个值在枚举类中都是一个对象。所以在我们使用时枚举的值将比整数常量消耗更多的内存。 那么我们最好使用常量来替代枚举。可是使用了常量代替后又不能限制取值了。上面这两个注解就是为了解决这个问题的。

@IntDef@StringDef 注解是 Android 提供的魔术变量注解,您可以创建整数集和字符串集的枚举来代理 Java 的枚举类。 它将帮助我们在编译代码时期像 Enum 那样选择变量的功能。 @IntDeftypedef 作用非常类似,你可以创建另外一个注解,然后用 @IntDef 指定一个你期望的整型常量值列表,最后你就可以用这个定义好的注解修饰你的 API 了。接下来我们来使用 @IntDef 来替换 Enum 看一下.

public class MainActivity extends Activity {
    public static final int SUNDAY = 0;
    public static final int MONDAY = 1;
    {...省略部分}

    @IntDef({SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY})
    @Retention(RetentionPolicy.SOURCE)
    public @interface WeekDays {
    }

    @WeekDays
    int currentDay = SUNDAY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setCurrentDay(WEDNESDAY);

        @WeekDays int today = getCurrentDay();
        switch (today) {
            case SUNDAY:
                break;
            case MONDAY:
                break;
            {...省略部分}
            default:
                break;
        }
    }

    /**
     * 参数只能传入在声明范围内的整型,不然编译通不过
     * @param currentDay
     */
    public void setCurrentDay(@WeekDays int currentDay) {
        this.currentDay = currentDay;
    }

    @WeekDays
    public int getCurrentDay() {
        return currentDay;
    }
}
复制代码

说明:

  1. 声明一些必要的 int 常量
  2. 声明一个注解为 WeekDays
  3. 使用 @IntDef 修饰 WeekDays,参数设置为待枚举的集合
  4. 使用 @Retention(RetentionPolicy.SOURCE) 指定注解仅存在与源码中,不加入到 class 文件中

需要在调用时只能传入指定类型,如果传入类型不对,编译不通过。

我们也可以指定整型值作为标志位,也就是说这些整型值可以使用 ’|’ 或者 ’&’ 进行与或等操作。如果我们把上面代码中的注解定义为如下标志位:

@IntDef(flag = true, value = {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY})
public @interface Flavour {
}
复制代码

那么可以如下调用:

setCurrentDay(SUNDAY & WEDNESDAY);
复制代码

@StringDef 同理。

自定义注解

Java 元注解

名字描述
@Retention标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
@Documented标记这些注解是否包含在用户文档中,即包含到 Javadoc 中去
@Target标记这个注解的作用目标
@Inherited标记这个注解是继承于哪个注解类
@RepeatableJava 8 开始支持,标识某注解可以在同一个声明上使用多次

@Retention
表示注解保留时间长短。可选的参数值在枚举类型 java.lang.annotation.RetentionPolicy 中,取值为:

  • RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,不会写入 class 文件;
  • RetentionPolicy.CLASS:注解只被保留到编译进行的时候,会写入 class 文件,它并不会被加载到 JVM 中;
  • RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以反射获取到它们。

@Target
用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。 可能的值在枚举类 java.lang.annotation.ElementType 中,包括:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上;
  • ElementType.FIELD:允许作用在属性字段上;
  • ElementType.METHOD:允许作用在方法上;
  • ElementType.PARAMETER:允许作用在方法参数上;
  • ElementType.CONSTRUCTOR:允许作用在构造器上;
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上;
  • ElementType.ANNOTATION_TYPE:允许作用在注解上;
  • ElementType.PACKAGE:允许作用在包上。

@Target 注解的参数也可以接收一个数组,表示可以作用在多种目标类型上,如: @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})

自定义注解

你可以根据需要自定义一些自己的注解,然后要在需要的地方加上自定义的注解。需要注意的是每当自定义注解时,相对应的一定要有处理这些自定义注解的流程,要不然可以说是没有实用价值的。注解真真的发挥作用,主要就在于注解处理方法。 注解的处理一般分为两种:

  • 保留注解信息到运行时,这时通过反射操作获取到类、方法和字段的注解信息,然后做相对应的处理
  • 保留到编译期,一般此方式是利用 APT 注释解释器,根据注解自动生成代码。简单来说,可以通过 APT,根据规则,帮我们生成代码、生成类文件。ButterKnife、Dagger、EventBus 等开源库都是利用注解实现的。

因为自定义注解的涉及到的内容较多。本期先不对自定义注解详细展开介绍,后续找时间再对它进行单独的文章讲解。

附参考文章

文章分类
Android