Android 中注解的使用

前言

Android Support Library 从 19.1 版本开始引入了一个新的注解库,其中包含了很多的元注解,使用它们修饰我们的代码, 可以让我们提高程序的开发效率,让我们更早的发现问题。以及对代码施以规范,让代码更加有可读性。这篇文章就来简单了解下这些注解,以及其使用。如有错误和遗漏,欢迎留言和补充~

注:现在我们新建项目直接就依赖了 support.appcompat 包,其中已经依赖了 annotations 包。如果你的项目中写如下注解报错,可以添加注解包:

dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}

@IntDef & @StringDef

替代 Java 中枚举的注解,以 @IntDef 为例,定义和使用如下:

@IntDef({RED, BLUE, YELLOW})
@Retention(RetentionPolicy.SOURCE)
public @interface LightColors{};

public static final int RED = 1;
public static final int BLUE = 2;
public static final int YELLOW = 3;

public void setColor(@LightColors int color){
}
  • @interface:声明新的枚举注解类型。
  • @Retention(RetentionPolicy.SOURCE):告知编译器不将枚举的注解数据存储在 .class 文件中。

如果允许常量与标志(例如:|、& 和 ^ 等等)相结合,则我们可以使用 flag 属性,如:

@IntDef(flag = true, value = {RED, BLUE, YELLOW})

使用:

setColor(RED | BLUE);

@Nullable & @NonNull

  • @Nullable:注解的元素可以为 null。
  • @NonNull:注解的元素不可以为 null。

上面的注解可以修饰如下元素:
1,方法参数。如:

@Nullable
private String data;

2,方法的返回值。 如:

@Nullable
public String getData(){
    return data;
}

3,成员属性。如:

public void setData(@Nullable String data){
}

当用空的参数传给被 @NonNull 修饰的方法参数的方法时,会给出如下警告提示(编译不会报错):

passing "null" argument to parameter annotated as @NotNull

@FloatRange & @IntRange

@FloatRange 和 @IntRange 是用于限定范围的注解。其中 @FloatRange 是限定 float 类型的,而 @IntRange 是限定 int 类型的。它们同上注解一样,可以修饰方法参数、方法返回值、成员属性。

以 @IntRange 为例,修饰方法参数的定义如下:

public void setAge(@IntRange(from = 1, to = 180) int age){
}

如果调用该方法传的参数不在 1 - 180 的范围内, 如:setAge(0),那么编译会直接报如下错:

value must be ≥ 1 and ≤ 180 (was 0)

@Size

@Size 注解的作用是限定长度的,同上注解一样,可以修饰方法参数、方法返回值、成员属性。

  • 限定字符串的长度:
public void setData(@Size(4) String data){
}

当传入的字符串长度不等于 4 时,编译器会直接报错:

Length must be exactly 4 
  • 限定数组的长度:
public void setData(@Size(4) int[] data){
}
  • 特殊的限定,如限定为 2 的倍数:
public void setData(@Size(multiple = 2) int[] data){
}

限定最小的长度:

@Size(min = 2)

限定最大的长度:

@Size(max = 2)

等同于 @Size(2) 写法:

@Size(value = 2)

@RequiresPermission

该注解作用是表明方法所执行的内容需要权限。如需要单个权限:

@RequiresPermission(Manifest.permission.CALL_PHONE)
private void callPhone(String phone){
}

需要一组权限:

@RequiresPermission(allOf = {
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE})
public static final void copyFile(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");

如果权限依赖于提供给方法参数的特定值,那么可以对参数本身使用 @RequiresPermission 而不用列出具体的权限,如 startActivity(intent) 方法:

public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle) {...}

当我们使用这种方式(间接权限)时,构建工具将执行数据流分析以检查传递到方法的参数是否具有任何 @RequiresPermission 注解。如:

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:1234567890"));
startActivity(intent);

这里的 startActivity(intent) 就直接报错了:

call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`

因为 Intent.ACTION_CALL 中标记了权限注解:

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@RequiresPermission(Manifest.permission.CALL_PHONE)
public static final String ACTION_CALL = "android.intent.action.CALL";

@CheckResult

@CheckResult 注解是作用于方法上的,作用是检验有没有处理返回值。如果没有处理返回值则会报错。

@CheckResult
public String getData(String data) {
    return data.trim();
}

线程注解

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

  • @MainThread:表示标记的方法只应在主线程调用。如果标记的是一个类,那么该类中的所有方法都应该是在主线程被调用。例:(通常,应用程序的主线程也是 Ui 线程。但是,在特殊情况下,应用程序的主线程可能不是其 Ui 线程)
@MainThread
public void deliverResult(D data) { ... }
  • @UiThread:表示标记的方法或构造函数只应该在 Ui 线程上调用。如果标记的是一个类,那么该类中的所有方法都应是在 Ui 线程被调用。例:
@UiThread
public abstract void setText(@NonNull String text) {...}
  • @WorkerThread:表示标记的方法只应该在工作线程上调用。如果标记的是一个类,那么该类中的所有方法都应是在一个工作线程上调用。例:
@WorkerThread
protected abstract FilterResults performFiltering(CharSequence constraint);
  • @BinderThread:表示标记的方法只应在绑定线程上调用。如果标记的是一个类,那么该类中的所有方法都应是在绑定线程被调用。例:
@BinderThread
public BeamShareData createBeamShareData() { ... }
  • @AnyThread:表示可以从任何线程调用带标记的方法。如果标记的是一个类,那么该类中的所有方法都可以从任何线程中调用。例:
@AnyThread
public void deliverResult(D data) { ... }

构建工具会将 @MainThread 和 @UiThread 注解视为可以互换,因此,我们可以从 @MainThread 方法调用 @UiThread 方法,反之亦然。不过如果系统应用在不同线程上带有多个试图,Ui 线程可与主线程不同。因此,我们应该使用 @UiThread 标注于应用的视图层次结构关联的方法,使用 @MainThread 仅标注于应用生命周期关联的方法。

资源注解

在 Android 中几乎所有的资源都有其对于的 id,我们在使用的时候可以直接通过 id 来,如:

textView.setText(getResources().getText(R.string.app_name));

但是这样如果没有写指定的资源注解的话就会风险,比如随便传了个 0,那么就会找不到对应的资源。
为了避免由于自己的粗心大意而引发的错误,我们就可以使用资源注解了,如:

public int getText(@StringRes int id){
}

这样当我们调用该方法时,如果传递的参数并不是 String 类型的资源 id,那么编译器就会报错提示。

除了 @StringRes 资源注解外,还有:

  • @IntegerRes:R.integer 类型资源。
  • @AnimatorRes:R.animator 类型资源。
  • @AnimRes:R.anim 类型资源。
  • @ArrayRes:R.array 类型资源。
  • @AttrRes:R.attr 类型资源。
  • @BoolRes:R.bool 类型资源。
  • @ColorRes:R.color 类型资源。
  • @DimenRes:R.dimen 类型资源。
  • @DrawableRes:R.drawable 类型资源。
  • @FractionRes:R.fraction 类型资源。(百分比)
  • @IdRes:R.id 类型资源。
  • @InterpolatorRes:R.interpolator 类型资源。(插值器)
  • @LayoutRes:R.layout 类型资源。
  • @MenuRes:R.menu 类型资源。
  • @PluralsRes:R.plurals 类型资源。(复数)
  • @RawRes:R.raw 类型资源。
  • @StyleableRes:R.styleable 类型资源。
  • @StyleRes:R.style 类型资源。
  • @TransitionRes: R.transition 类型资源。
  • @XmlRes:R.xml 类型资源。
  • @AnyRes:未知资源。(表示自己不知道是什么类型的资源。比如有可能为 R.drawable 也有可能是 R.string。)

@ColorInt

@ColorInt 注解的作用为:限定颜色值。(ARGB:0xAARRGGBB)

public void setColor(@ColorInt int color) {

}

如果直接使用资源 id,则会报错,如下:

setColor(R.color.colorAccent)// 报错

正确的使用是:

setColor(0xFFFF00FF);

如果要使用资源 id,则可以通过 ContextCompat.getColor() 方法来:

setColor(ContextCompat.getColor(context, R.color.colorAccent));

@CallSuper

该注解用于修饰方法,表示重写该方法时必须调用 super 方法。如 onCreate() 方法:

@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}

重写 onCreate() 方法时,必须调用 super 方法:

super.onCreate(savedInstanceState);

否则报错。

@VisibleForTesting & @Keep

使用 @VisibleForTesting 和 @Keep 注解可以表示方法、类、或字段的可访问性。

  • @VisibleForTesting:该注解只起到一个注释的作用,告诉其他开发者被标记的代码为什么有这么大的可见程度(为了测试方便)。因此,经常用来修饰 public 或 protected,用来修饰 private 并不会报错,但是没有意义。

  • @Keep:标记的指定代码在混淆时不会被混淆。

参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,275评论 25 707
  • 什么是注解 注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的@Override);...
    张明云阅读 25,092评论 6 154
  • Default Parameters(默认参数) in ES6 Template Literals (模板文本)i...
    Otherthing阅读 176评论 0 0
  • 昨晚八点下着雨,从帐蓬里冒雨伸出来随手一拍,多美的天空啊! 雨从昨天一直下到今天,早上七点多铁路保安又来叫我离开。...
    环华小蜗牛阅读 205评论 0 1
  • 一杯温开水,半本胡雪岩,偷得浮生半日闲,忘却窗外是何年。数篇短文章,百转冷热肠,为谁辛苦为谁忙?昨日因,今日果,且...
    自由和安阅读 361评论 2 1