Android Annotation注解详解

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119874435
本文出自【赵彦军的博客】

Java注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

Java内置了多种标准注解,其定义在java.lang中。

  • Override 表示当前的方法定义将覆盖父类中的方法
    在这里插入图片描述

  • Deprecated 被此注解标记的元素表示被废弃
    在这里插入图片描述

  • SuppressWarnings 关闭不当的编译器警告信息
    在这里插入图片描述
    在上面我们看到了 @Target 、@Retention , 这些也是注解,我们暂且可以称之为注解的注解。

元注解说明

@Retention

表示需要在什么级别保留该注解信息

  • RetentionPolicy.SOURCE:只在源代码中保留 一般都是用来增加代码的理解性或者帮助代码检查之类的,比如我们的Override
  • RetentionPolicy.CLASS : 默认的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运行时是无法得到的
  • RetentionPolicy.RUNTIME :注解不仅能保留到class字节码文件中,还能在运行通过反射获取到,这也是我们最常用的

@Target

表示该注解可以用在什么地方

  • ElementType.FIELD : 能修饰成员变量
  • ElementType.METHOD:能修饰方法
  • ElementType.CONSTRUCTOR : 能修饰构造器
  • ElementType.PACKAGE : 能修饰包
  • ElementType.PARAMETER : 能修饰方法参数
  • ElementType.TYPE : 能修饰类、接口或枚举类型
  • ElementType.ANNOTATION_TYPE : 能修饰注解
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType.MODULE :

通过反射获取注解信息

Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。

首先我们定义一个端口注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Port {
    String value() default "8080";
}

RetentionPolicy.RUNTIME 在运行时保留注解,作用于是字段。

反射获取字段

object BindPort {

    /**
     * 绑定的目的
     * 1、通过反射获取注解的值
     * 2、通过反射给目标设置值
     */
    fun bind(activity: Activity) {
        //获取所有字段
        val fields = activity.javaClass.declaredFields
        fields.forEach { field ->
            //获取所有注解
            val ans = field.annotations
            ans.forEach {
                if (it is Port) {
                    //获取注解值
                    var port = it.value

                    //通过属性反射给属性注入值
                    field.isAccessible = true
                    field.set(activity, port)
                }
            }
        }
    }

}

上面代码的逻辑很简单:

首先遍历循环所有的属性,如果当前属性被指定的注解所修饰,那么就将当前属性的值修改为注解中成员变量的值。

这里setAccessible(true)的使用时因为,我们在声明port变量时,其类型为private,为了确保可以访问这个变量,防止程序出现异常。

注解的使用:


class MainActivity : AppCompatActivity() {

    @Port
    var port: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d("yy--", "反射前:$port")

        BindPort.bind(this)

        Log.d("yy--", "反射后:$port")

    }
}

运行结果:

com.example.myapplication D/yy--: 反射前:null
com.example.myapplication D/yy--: 反射后:8080

当然,我们也可以在注解时,自定义我们的属性值,比如:

class MainActivity : AppCompatActivity() {

    @Port("8090")
    var port: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d("yy--", "反射前:$port")

        BindPort.bind(this)

        Log.d("yy--", "反射后:$port")

    }
}

运行结果:

com.example.myapplication D/yy--: 反射前:null
com.example.myapplication D/yy--: 反射后:8090

方法使用注解

我们来模拟一个http请求, 定义一个 Request 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Request {

    Method value();

    enum Method {
        GET, POST
    }
}

注解的方式简单,@Retention(RetentionPolicy.RUNTIME) 在运行时保留,@Target(ElementType.METHOD) 作用域在方法上。

下面我们编写,反射的方法,定义 HttpBind

object HttpBind {

    /**
     * 绑定的目的
     * 1、通过反射获取注解的值
     * 2、通过反射给目标设置值
     */
    fun bind(activity: Activity) {
        //获取所有方法
        val methods = activity.javaClass.methods
        methods.forEach { method ->
            //获取所有注解
            val ans = method.annotations
            ans.forEach {
                if (it is Request) {
                    //获取注解值
                    var requestMethod = it.value
                    if (requestMethod == Request.Method.GET) {
                        //发起get请求
                        method.invoke(activity, requestMethod.name)
                    } else if (requestMethod == Request.Method.POST) {
                        //发起post请求
                        method.invoke(activity, requestMethod.name)
                    }
                }
            }
        }
    }

}

注解使用

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //使用反射,解析注解
        HttpBind.bind(this)
    }

    @Request(Request.Method.GET)
    fun http(method: String) {
        Log.d("yy--", "网络请求:$method")
    }
}

我们运行一下,看看效果

D/yy--: 网络请求:GET

方法的参数使用注解

先定义一个参数注解 Path

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Path {
    String value() default "";
}

这个注解也很简单,在运行时保留,作用域在参数上

下面我们使用反射来获取参数注解的值

object HttpBind {

    /**
     * 绑定的目的
     * 1、通过反射获取注解的值
     * 2、通过反射给目标设置值/反射调用方法
     */
    fun bind(activity: Activity) {
        //获取所有方法
        val methods = activity.javaClass.methods
        methods.forEach { method ->
            //获取所有参数注解,一个方法有多个参数,一个参数有多个注解,所以类型是二维数组
            val ans = method.parameterAnnotations
            ans.forEach { annotationArray ->
                annotationArray.forEach { parameterAnnotation ->
                    if (parameterAnnotation is Path) {
                        var parameter = parameterAnnotation.value
                        method.invoke(activity, parameter)
                    }
                }
            }
        }
    }
}

使用如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        HttpBind.bind(this)
    }

    fun http(@Path("zhaoyanjun") user: String) {
        Log.d("yy--", "参数注解:$user")
    }
}

运行结果如下:

D/yy--: 参数注解:zhaoyanjun

Android 自带的注解

Android 系统已经帮我内置了很多有用的注解,在我们的开发过程中可以很方便的使用。

 implementation 'androidx.annotation:annotation:1.2.0'

示例如下
在这里插入图片描述
我们举几个常见的例子:

资源限制类

  • @AnimatorRes :animator资源类型
  • @AnimRes:anim资源类型
  • @AnyRes:任意资源类型
  • @ArrayRes:array资源类型
  • @AttrRes:attr资源类型
  • @BoolRes:boolean资源类型
  • @ColorRes:color资源类型
  • @DimenRes:dimen资源类型。
  • @DrawableRes:drawable资源类型。
  • @FractionRes:fraction资源类型
  • @IdRes:id资源类型
  • @IntegerRes:integer资源类型
  • @InterpolatorRes:interpolator资源类型
  • @LayoutRes:layout资源类型
  • @MenuRes:menu资源类型
  • @PluralsRes:plurals资源类型
  • @RawRes:raw资源类型
  • @StringRes:string资源类型
  • @StyleableRes:styleable资源类型
  • @StyleRes:style资源类型
  • @TransitionRes:transition资源类型
  • @XmlRes:xml资源类型

线程限制类

Thread annotations 线程执行限制类:用于限制方法或者类必须在指定的线程执行。如果方法代码运行线程和标注的线程不一致,则会导致警告。

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

数值限制类

Value Constraint Annotations 类型范围限制类:用于限制标注值的值范围

  • @FloatRang
  • @IntRange

@LayoutRes

这个是 layout 资源类型,我们看一下 ActivitysetContentView 源码:

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

本质上,layoutResID 是一个 int 类型,如果不做限定的话,可以传入任意整形,但是有 @LayoutRes 注解的限制,值只能传入 R.layou.xx , 如果传入其他的类型就会报错。举例如下:
在这里插入图片描述
需要注意的是,报错只是编译器的检查出错,提醒开发者改正错误用法,提前规避风险,并不影响编译运行

@MainThread

限定方法执行的线程,如果方法代码运行线程和标注的线程不一致,不会报错,更多是起一个提醒作用

  @MainThread
   fun run() {

  }

@IntDef

IntDef 的源码如下:

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;

    /**
     * Whether any other values are allowed. Normally this is
     * not the case, but this allows you to specify a set of
     * expected constants, which helps code completion in the IDE
     * and documentation generation and so on, but without
     * flagging compilation warnings if other values are specified.
     */
    boolean open() default false;
}

可以看到 TargetANNOTATION_TYPE 说明 IntDef 是作用在注解上的。

还有一个 valueint 数组。

下面我们定义一个注解 MOBILE_TYPE , 并且用 IntDef 修饰,如下:

import androidx.annotation.IntDef;
import kotlin.annotation.AnnotationRetention;
import kotlin.annotation.Retention;

/**
 * @author : zhaoyanjun
 * @time : 2021/7/29
 * @desc :
 */
public class Util {

    public static final int TYPE_MI = 1;
    public static final int TYPE_MEIZU = 2;
    public static final int TYPE_HUAWEI = 3;

    @Retention(AnnotationRetention.SOURCE)
    @IntDef({TYPE_MI, TYPE_MEIZU, TYPE_HUAWEI})
    public @interface MOBILE_TYPE {
    }
    
    //使用
    public void doSomething(@MOBILE_TYPE int mobile){
        
    }
}

使用方法很简单,首先定义你需要的常量,然后用 @IntDef 包住这些常量,这样别人在使用你的方法时如果输入的值不在枚举的范围内,编译器就会给出提示了。

同理,@StringDef 也是同样的用法

import androidx.annotation.StringDef;

import kotlin.annotation.AnnotationRetention;
import kotlin.annotation.Retention;

/**
 * @author : zhaoyanjun
 * @time : 2021/7/29
 * @desc :
 */
public class Util {

    public static final String TYPE_HD = "720p";
    public static final String TYPE_SHD = "1080p";
    public static final String TYPE_FHD = "4k";

    @Retention(AnnotationRetention.SOURCE.SOURCE)
    @StringDef({TYPE_HD, TYPE_SHD, TYPE_FHD})
    public @interface DISPLAY_TYPE {
    }

    public void doSomething(@DISPLAY_TYPE String display) {

    }
}

还有一个 @LongDef 也是同样的用法,这里就不举例了。

总结 :IntDef @StringDef @LongDef 可以限制变量的类型,可以代替枚举类型

我们来看一个系统例子,Toast 的源码:

  public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }

@Duration 是一个自定义的注解:


    /** @hide */
    @IntDef(prefix = { "LENGTH_" }, value = {
            LENGTH_SHORT,
            LENGTH_LONG
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Duration {}

看到这里,我们已经很熟悉了,也是用的 @IntDef 注解,除此之外,我们还发现了一个细节 ,在 android 的注解包里,@IntDef 带有 prefix 属性,但是在 androidx 的注解包里却没有。

下面贴一下两个的源码,大家看看:

//android 的源码,包名:android.annotation
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the constant prefix for this element */
    String[] prefix() default {};
    /** Defines the constant suffix for this element */
    String[] suffix() default {};

    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;
}

//androidx 的源码,包名:androidx.annotation
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;

    /**
     * Whether any other values are allowed. Normally this is
     * not the case, but this allows you to specify a set of
     * expected constants, which helps code completion in the IDE
     * and documentation generation and so on, but without
     * flagging compilation warnings if other values are specified.
     */
    boolean open() default false;
}

这是两个包下面的 IntDef 的差异,我想知道的是 prefix 有什么用?
其实也很简单,规范 value 数组元素的命名前缀。

@StringRes

这个其实很好理解,限制字符的来源,必须是 R.string.xx , StringRes 源码如下:

/**
 * Denotes that an integer parameter, field or method return value is expected
 * to be a String resource reference (e.g. {@code android.R.string.ok}).
 */
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}

举个系统的例子:

    public static Toast makeText(Context context, @StringRes int resId, @Duration int duration)
                                throws Resources.NotFoundException {
        return makeText(context, context.getResources().getText(resId), duration);
    }

@ColorInt

限定颜色的取值范围 R.color.xx , 源码如下:

/**
 * Denotes that the annotated element represents a packed color
 * int, {@code AARRGGBB}. If applied to an int array, every element
 * in the array represents a color integer.
 * <p>
 * Example:
 * <pre>{@code
 *  public abstract void setTextColor(@ColorInt int color);
 * }</pre>
 */
@Documented
@Retention(CLASS)
@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
public @interface ColorInt {
}

举个系统的例子:

public void setTextColor(@ColorInt int color) {
    mTextColor = ColorStateList.valueOf(color);
    updateTextColors();
}

@IdRes

限制id 的取值范围:R.id.xx , 源码码如下:

/**
 * Denotes that an integer parameter, field or method return value is expected
 * to be an id resource reference (e.g. {@code android.R.id.copy}).
 */
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface IdRes {
}

举个系统的例子:

 @Override
 public <T extends View> T findViewById(@IdRes int id) {
    return getDelegate().findViewById(id);
 }

@DrawableRes

限定资源的取值类型是一个 drawable 类型:android.R.attr.alertDialogIcon

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface DrawableRes {
}

举个系统的例子:

public void setImageResource(@DrawableRes int resId) {
   ...
}

@NotNull

定义个变量不能为空, 如果真的为空,不会影响编译,只是编译器会报错,提醒开发者注意。

public class Util {

    //参数不能为null
    public void run(@NotNull String name) {

    }
}

测试:
在这里插入图片描述
可以看到编译器会自动检查

@Nullable

限定一个参数,一个方法的返回值可以为null

public class Util {

    @Nullable
    public String aa() {
        return null;
    }
}

使用:
在这里插入图片描述
编译器会自动提示

@Keep

哪里不想被混淆就注解哪里。

@Keep
public class Test {
}

public class TestA {
}

开始混淆打包,查看混淆后的结果:

在这里插入图片描述
我们发现TestA不见了而Test保留了下来,说明我们的配置起作用了,下面我们在Test 类中增加点内容看看混淆后会变成什么样子,修改后的类内容如下:

@Keep
public class Test {
    int age = 20;
    protected String sex = "m";
    public String name = "CodingMaster";
}

查看混淆后的结果:

在这里插入图片描述
不幸的是虽然类名保留下来了,但是里面的内容却被混淆了,如果我们想把name变量不被混淆怎么办呢?

我们继续修改Test类,这次我们多加了点东西,会在后面用到,内容如下:

@Keep
public class Test {
    int age = 20;
    @Keep
    protected String sex = "m";
    @Keep
    public String name = "CodingMaster";

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void cry(){

    }
}

重新混淆查看结果:

在这里插入图片描述

我们的name变量被成功的保留了,同理如何保留被sex变量呢?这里就不买关子了,直接给出答案,为sex添加@Keep注解就可以了,持怀疑态度的同学👨‍🎓可以自己去验证。
细心的同学可能已经发现,Test类里面的方法都被混淆了,怎样指定某个方法不被混淆呢?

然后为cry()方法添加@Keep注解,重新混淆查看结果:

在这里插入图片描述
有没有很简单的感觉呢?哪里不混淆@Keep哪里,再也不用为混淆头疼了!

@RequiresPermission

限定字段,方法需要某个权限,如果没有,编译器会提醒

public class Util1 {

@RequiresPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
public String run() {
    return null;
}

使用:
在这里插入图片描述
看到编译器报错,我们点击一下 Add permission check , 编译器会自动帮我们补全代码

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != 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
        }
        Util1().run()
    }

}
  • @RequiresPermission(permision)
  • @RequiresPermission(allOf={permision1,perminsion2})
  • @RequiresPermission(anyOf={permision1,perminsion2})

@Deprecated

标记某个字段或者方法过时,举例:
在这里插入图片描述

CallSuper

子类重写某个方法时,要求调用super,可以使用该注解

@IntRange

    //限定只能传1-4
    fun run(@IntRange(from = 1, to = 4) num: Int) {

    }

使用:
在这里插入图片描述

@FloatRange

用法上和 IntRange 一样,

    //限定只能传1-4
    fun run(@FloatRange(from = 1.0, to = 4.0) num: Float) {

    }

源码如下:

在这里插入图片描述

其中,fromInclusive 是否包含 from ,toInclusive 是否包含 to , 其实就是左包含,右包含的意思。

@CheckResult

假设你定义了一个方法返回一个值,你期望调用者用这个值做些事情,那么你可以使用@CheckResult注解标注这个方法,强制用户定义一个相应的返回值,使用它!

首先定义 CallSuperT ,定义一个retrunI方法返回一个int类型

public class CallSuperT {

    @CheckResult
    public int retrunI(){
        return 1;
    }
}

正确调用:

public class MainActivity extends AppCompatActivity {

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

        CallSuperT callSuperT = new CallSuperT();
        int returns = callSuperT.retrunI();
    }
}

如果这里去掉返回类型的定义对象:int returns则会抛出异常

public class MainActivity extends AppCompatActivity {

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

        CallSuperT callSuperT = new CallSuperT();
        callSuperT.retrunI();
    }
}

错误提示结果:

在这里插入图片描述

@size

定义长度大小,可选择最小和最大长度使用

public class MainActivity extends AppCompatActivity {

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

        testDo("111");

        testDo("1");
    }

    private void testDo(@Size(min = 1,max = 2)String s){
        Log.e("tag","-------->"+s);
    }
}

错误提示结果:

在这里插入图片描述

这里size定了一个最小和最大长度,所以只有testDo(“1”)符合条件,其他调用都抛出了异常

总结:

注解的作用:

  • 提高我们的开发效率
  • 更早的发现程序的问题或者错误
  • 更好的增加代码的描述能力
  • 更加利于我们的一些规范约束
  • 提供解决问题的更优解
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值