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

《Android注解学习笔记》 @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注解,我们就可以看到这样的错误提示:

《Android注解学习笔记》 @StringRes

线程注解

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

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

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

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

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

《Android注解学习笔记》 @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) {
        
    }

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

《Android注解学习笔记》 @IntRange

权限注解

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

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

    }

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

《Android注解学习笔记》 没有CALL_PHONE权限

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

《Android注解学习笔记》 加入了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;
    }

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

《Android注解学习笔记》 @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注解,这个时候子类中重写的方法就会提示这样的错误。

《Android注解学习笔记》 @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就是两个静态常量。这时候再看调用的地方。

《Android注解学习笔记》 @IntDef

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

总结

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

    原文作者:安德雷士
    原文地址: https://www.jianshu.com/p/a5b89356eed0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞