Android注解-什么?啥是注解?干啥的?

首先带入问题。
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会弹出类似的报错。


TargetError.png

元注解是注解中的一种,他通过 @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) 那么也可以称他为元注解。



2 注解的作用
  • @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为此做出了很大贡献。 https://github.com/square/javapoet 具体使用方法请搜索引擎,输入Javapoet教学 [手动狗头]

对于第三个问题,注解的生命周期,编译时注解和运行时注解区别
你都看到这了,应该有个大致答案了。

  • 首先声明周期就不一样,一个在编译期class字节码阶段。一个被ClassLoder 从class字节码到内存。
  • 编译时期注解一般会生成各种代码实现各种操作。运行时期注解,程序运行时期获取到你使用的各种注解,例如retrofit 运行时注解 加 动态代理完成网络请求的各种操作。
  • //todo以后想到了继续补充
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容