Android注解学习笔记

最近在看一些开源项目的源码,发现了Android中的一些很有意思的注解,于是归纳总结了一下,以后在自己的项目中也可以尝试使用。

首先,需要在gradle的dependencies中加入
compile 'com.android.support:support-annotations:25.2.0'
当前的最新版本是25.2.0

Android注解有8种类型,分别是Nullness注解、资源类型注解、线程注解、变量限制注解、权限注解、结果检查注解、CallSuper注解、枚举注解。

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


@NonNull

如果我们将这个函数改为:

    private void testNonNull(@Nullable String test) {
        
    }

或者没有任何注解时,就没有提示了。

资源类型注解

所有以“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注解,我们就可以看到这样的错误提示:

@StringRes

线程注解

包括@AnyThread、@UiThread和@WorkerThread,表明需要运行在什么线程上。

@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface WorkerThread {
}

例如有一个函数,需要做一些耗时操作,我们希望它不要运行在主线程上

    @WorkerThread
    private void testThread() {
        // 耗时操作
    }

如果我们在主线程中调用这个函数会怎么样呢?


@WorkerThread

而如果这样调用就不会有问题,这样就保证了我们这个耗时操作不会执行在主线程中。

        new Thread(new Runnable() {
            public void run() {
                testThread();
            }
        }).start();

变量限制注解

变量限制注解主要包含@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) {
        
    }

如果调用者输入了一个超出范围的值时,会这样提示他。


@IntRange

权限注解

如果我们有方法需要使用某种权限,可以加上@RequiresPermission这个注解。

    @RequiresPermission(Manifest.permission.CALL_PHONE)
    private void testPermission() {

    }

比如这里需要打电话的权限,但是我并没有在应用中加入该权限。


没有CALL_PHONE权限

当我们调用函数时,就会有这样的错误提示。好吧,那我把权限加上,发现还是有错误提示。


加入了CALL_PHONE权限

没有错,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();

这样就没有问题了。

结果检查注解

如果我们写了一个有返回值的函数,并且我们希望调用者对这个返回值进行使用或者检查。这个时候@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;
    }

如果我们不关心他的返回值。

@CheckResult

提示我们结果没有被使用。如果改为boolean result = testCheckResult();就不会有问题了。@CheckResult注解保证了我们方法的返回值一定会得到使用。

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注解,这个时候子类中重写的方法就会提示这样的错误。


@CallSuper

这样就提醒我们需要加上super的方法。

枚举注解

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.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就是两个静态常量。这时候再看调用的地方。


@IntDef

是不是非常熟悉?没错,我们熟悉的Toast就是用@IntDef注解这么实现的。感兴趣的可以去看看源码。

总结

发现注解是一个非常有意思的东西,他可以让我们在很早的时候就发现问题,团队协作起来也更有效率,所以做了一些总结,希望以后在项目中能够多多用到注解。

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

推荐阅读更多精彩内容