看完这篇文章你会了解到什么

  1. 什么是注解
  2. 注解有什么用,咱们为什么要用注
  3. 注解的生命周期,编译时注解和运转时注解区别

引用他人对注解的解说,注解能够了解成标签。

什么是注解

在代码中咱们最常见的注解应该是他 @Override ,用于重载父类的办法。

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

点击 @Override 进到源码,中咱们会发现下面这种结构

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

那么,参考源码的完结方式咱们这样就能够生成自己的注解

public @interface TestAnnotation {
}

在咱们代码中进行调用

@TestAnnotation
private void logOut() {
    Log.i(TAG, "logOut: ");
}

只不过这种注解完结,除了能增加代码量,其他毫无意义 [手动狗头]。 上面 @Override 注解中呈现的 @Target@Retention 这两个东西,一看感觉便是注解的参数,点进源码会发现官方给他们起了一个专门的姓名,meta-annotation(元注解),其只能,表明声明的类型,仅用作杂乱注释类型声明中的成员类型。它不能用于直接注释任何内容。(意思对注解进行注解)

/**
* <p>This {@code @Target} meta-annotation indicates that the declared type is
* intended solely for use as a member type in complex annotation type
* declarations.  It cannot be used to annotate anything directly:
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

假如咱们在其他的地方调用,as会弹出类似的报错。

Android注解

元注解是注解中的一种,他经过 @Target(ElementType.ANNOTATION_TYPE) 指定其运用场景,只能用在注解上,即对注解进行注解

Target 支撑的类型

ElementType 类型规模
TYPE 类、接口(包含注释类型)或枚举声明
FIELD 字段声明(包含枚举常量
METHOD 办法
PARAMETER 参数
CONSTRUCTOR 结构办法
LOCAL_VARIABLE 局部变量
ANNOTATION_TYPE 注解
PACKAGE
TYPE_PARAMETER 类型参数(泛型)
TYPE_USE 运用类型注解

@Retention 指定注解的生命周期

SOURCE CLASS RUNTIME
生命周期 源码阶段 Class文件阶段 运转
解说 .java文件(仅在咱们开发过程中存在,
提示过错或正告)
编译后从java变成.class文件
保存在字节码
ClassLoder 加载class字节码到内存

那么对第一个问题的总结 什么是注解,在代码层面上,只需运用@interface 的都是注解。假如咱们指定他的Target在annotation上。@Target(ElementType.ANNOTATION_TYPE) 那么也能够称他为元注解。

注解的效果

@Retention(RetentionPolicy.SOURCE)

源代码时期的注解,仅存在于.java文件中

  1. 用于代码检查例如 @NonNull /@Nullable ,资源引用约束例如 @DrawableRes@StringRes,提醒过错,过期例如 @Deprecated
  2. 在编译期间处理,.java文件编译生成class文件时期,经过开发者注册的注解处理器(AnnotationProcessor)对注解进行处理,运用JavaPoet生成模板代码,提高开发功率。

@Retention(RetentionPolicy.RUNTIME)

  1. 注解在class字节码文件中存在,经过反射获取到注解的目标以及参数等信息供给调用。简略完结但反射耗费功能不建议过度运用。

举个栗子 完善一下咱们之前的TestAnnotation 增加两个参数,name 和 age

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age();
}

在创立一个UserBean类

public class UserBean {
    private String name;
    private int age;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    private String getName() {
        return name;
    }
    @FunctionAnnotation
    private void setName(@FunctionType String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{"
                + "name='" + name + '\''
                + ", age=" + age
                + '}';
    }
}

创立用于绑定的Utils

public class AnnotationUtils {
    public static void Inject(Activity activity) {
        //万物皆目标。获取activity的class
        Class<? extends Activity> c = activity.getClass();
        // 获取所有字段
        Field[] fields = c.getDeclaredFields();
        //遍历拿到带有 @TestAnnotation 注解的字段
        for (Field field : fields) {
            if (field.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                if (annotation == null) {
                    break;
                }
                //TestAnnotation name
                String name = annotation.name();
                int old = annotation.age();
                //类型的包名途径 例如:com.example.demo.data.UserBean
                String packName = field.getType().getName();
                //手动界说的目标名称,此例子运用的是mUser
                String fieldName = field.getName();
                try {
                    //创立一个user 这儿运用包名途径
                    Class<?> fieldClass = Class.forName(packName);
                    UserBean user = (UserBean) fieldClass.newInstance();
                    //回来此Class目标对应类的、带指定形参列表的办法
                    Method declaredMethod = fieldClass.getDeclaredMethod("setName", String.class);
                    //设置能够拜访私有权限
                    declaredMethod.setAccessible(true);
                    //调用办法
                    declaredMethod.invoke(user, name);
                    Method declaredMethodSetOld = fieldClass.getDeclaredMethod("setAge", int.class);
                    declaredMethodSetOld.setAccessible(true);
                    declaredMethodSetOld.invoke(user, old);
                    //设置能够拜访私有权限
                    field.setAccessible(true);
                    //set目标
                    field.set(activity,user);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MainActivity中运用AnnotationUtils注入UserBean 目标

public class MainActivity extends Activity {
    @TestAnnotation(name = "xiao ming", old = 18)
    private UserBean mUser;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AnnotationUtils.Inject(this);
        System.out.println(" main activity user is " + mUser);
    }
}

运转程序咱们会得到如下的日志打印

I/System.out:  main activity user is User{name='xiao ming', age=18}

小结

emmm,搞了一圈你就给我看这个?我直接new一个目标set进属性不就完了吗。搞这么一圈四不四有缺点。 关于咱们自己写代码的话的确如此。并且代码业务越简略/单一,越不需要反射和注解。假如只需完结一句hello world,那搞其他操作真是弄巧成拙。

我了解是这样的

  • 反射 的场景在于,咱们需要运用他人的代码或许android源码(依靠库)。且无法直接修正,或许调用,他人写的代码的情况。迫不得已 咱们能够尝试运用反射去操作修正他人的目标,或许调用办法。

  • 注解嘛,就厉害了。咱们需要给他人供给服务,或许依靠库的时分。运用者经过运用,咱们开发者界说的注解,依照咱们界说的规则去标示运用者的代码,从而为开发者完结某些功用。便利运用者运用,和了解。 例如:retrofit ,咱们便是运用者,依照retrofit开发者界说好的各种注解,例如 @GET@POST,依照retrofit开发者界说的规则去标示咱们的接口办法。开发者经过读取咱们的注解,帮咱们完结各种逻辑。其他运用注解的框架同理,都是为了运用者便便利调用。 那么假如咱们,需要给他人供给服务,或许咱们要写依靠库的时分,就能够考虑是否用注解去完结了。

  • 假如上面说的,了解了。那应该也会了解,为什么上面举的栗子,分明只用反射也能够实习给user 设置 name 和 age,却偏偏要用注解了吧。(先后关系,先有咱们界说好注解的TestAnnotation注解的规则,后面才有开发者运用TestAnnotation 注解去标示给User运用)。

注解的优势

  • 源码时期注解提示安全以及报错
  • 编译时期注解,在编译阶段能够为咱们生成代码,完结各种功用例如早期的 JakeWharton 大神Butterknife ,生成代码完结findviewById操作。以及Goodle HIlt 依靠注入。 那咱们如何完结编译时注解呢。Javapoet为此做出了很大奉献。 github.com/square/java…
  • 调用者运用注解的时分,代码简练

注解的生命周期,编译时注解和运转时注解区别

  • 首先声明周期就不一样,一个在编译期class字节码阶段。一个被ClassLoder 从class字节码到内存。
  • 编译时期注解一般会生成各种代码完结各种操作。运转时期注解,程序运转时期获取到你运用的各种注解,例如retrofit 运转时注解加动态署理完结网络恳求的各种操作。
  • 运转时注解一般会经过反射的操作,影响功能。