Android注解学习笔记

作者: 安德雷士 | 来源:发表于2017-03-20 21:10 被阅读104次

    最近在看一些开源项目的源码,发现了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注解这么实现的。感兴趣的可以去看看源码。

    总结

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

    相关文章

      网友评论

        本文标题:Android注解学习笔记

        本文链接:https://www.haomeiwen.com/subject/ugwrgttx.html