美文网首页
Android中的注解

Android中的注解

作者: 水言 | 来源:发表于2018-09-25 19:34 被阅读22次

    Java在1.5的时候引入了注解。注解也被称为元数据,即一种描述数据的数据。
    注解是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。

    注解一般起到说明、配置的功能。
    注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用

    元注解

    元注解可以理解为注解的注解,在java.lang.annotation中
    包括@Retention @Target @Document @Inherited四种。

    1.@Retention: 定义注解的保留策略

    @Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中
    @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,运行时、无效
    @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到。

    它们的生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

    2.@Target:定义注解的作用目标

    @Target(ElementType.TYPE) //接口、类、枚举、注解
    @Target(ElementType.FIELD) //字段、枚举的常量
    @Target(ElementType.METHOD) //方法
    @Target(ElementType.PARAMETER) //方法参数
    @Target(ElementType.CONSTRUCTOR) //构造函数
    @Target(ElementType.LOCAL_VARIABLE)//局部变量
    @Target(ElementType.ANNOTATION_TYPE)//注解
    @Target(ElementType.PACKAGE) //包

    3.@Document:说明该注解将被包含在javadoc文档中,使用javadoc命令生成的文档中会带上该注解的内容
    4.@Inherited:@Inherited修饰注解A,父类中使用了注解A,子类可以继承父类中的注解A,只有在类上使用时才会有效,接口实现,接口继承,方法等无效。

    Java内建注解

    1. @Override——当我们想要复写父类中的方法时,我们需要使用该注解去告知编译器我们想要复写这个方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。

    2. @Deprecated——当我们希望编译器知道某一方法不建议使用时,我们应该使用这个注解。Java在javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。

    3. @SuppressWarnings——这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。它的保留策略是SOURCE(译者注:在源文件中有效)并且被编译器丢弃。

    4.@SafeVarargs (java7新增):去除“堆污染”警告

    5.@Functionlnterface (java8新增):修饰函数式接口

    自定义注解

    先来看看内建注解@Override
    java.lang.Override

    @Target(ElementType.METHOD) //作用目标是方法
    @Retention(RetentionPolicy.SOURCE) //在源码中有效果
    public @interface Override {
    }
    

    再看看ButterKnife的BindView 注解:
    butterknife.BindView

    @Retention(CLASS)  //在class文件有效
    @Target(FIELD)  //修饰属性
    public @interface BindView {
      @IdRes int value();
    }
    

    自定义注解的流程:
    1 .使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口。在定义注解时,不能继承其他的注解或接口。
    2 .使用元注解修饰注解
    3 .有需要给注解加上属性

    自定义最简单的注解:

    public @interface MyAnnotation {
    }
    
    • 注解中属性以无参方法形式展示;
    • 注解方属性类型限定为:基本类型、String、Enums、Annotation或者是这些类型的数组;
    • 注解属性可以有默认值;
    • 注解本身能够包含元注解,元注解被用来注解其它注解。

    自定义一个复杂的注解:

    @Documented
    @Target(ElementType.METHOD)
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        String name() default "abc";
        String date();
        int version() default 1;
        String[] comments();
    }
    

    自定义注解使用:

    @MyAnnotation(name = "bcd", date = "Nov 17 2012", version = 2,comments = {"xx","yy"})
    public String test() {
      return "test method";
    }
    

    注解工作原理

    注解最大的区别是它们的作用域不同,导致在处理注解的时候区别特别大。

    RetentionPolicy.SOURCE

    源码级别注解,比如开发中用到的@Override。
    看个自定义注解的例子:

    public static final int DEFAULT = 0;
    public static final int LARGE = 1;
    //自定义一个源码级别的注解
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({LARGE, DEFAULT})  //使用Android 内建注解,限定取值范围
    public @interface ProgressDrawableSize {}
    
    //调用该方法只能设置0和1,其他数字会报错
    public void updateSizes(@ProgressDrawableSize int size) {
    }
    

    RetentionPolicy.CLASS

    编译期注解,定义的注解为@Retention(RetentionPolicy.CLASS),在编译器生成class文件的时候生效。

    APT简介
    APT(Annotation Processing Tool 的简称),简单描述就是可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。现在有很多主流库都用上了 APT,比如 Dagger2, ButterKnife, EventBus3 等。
    关于annotationProcessor 替代apt可以参考:https://blog.csdn.net/xx326664162/article/details/68490059/

    定义注解处理器Processor
    需要处理RetentionPolicy.CLASS期间的注解需要自己定义一个Processor。
    自定义Processor时候需要继承javax.annotation.processing.AbstractProcessor。
    关于创建Processor有几个特殊要求:

    • 需要单独新建一个独立的java的lib,注意是plugin: 'java-library',而不是plugin: 'com.android.library'
    • bulid文件中添加Java7的说明,JavaVersion.VERSION_1_7
    public class MyProcessor extends AbstractProcessor {
        /**
         * init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。
         * ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
         * @param processingEnv 提供给 processor 用来访问工具框架的环境
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
        }
    
        /**
         * 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
         * 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素
         * @param annotations   请求处理的注解类型
         * @param roundEnv  有关当前和以前的信息环境
         * @return  如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;
         *          如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return false;
        }
    
        /**
         * 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
         * @return  注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotataions = new LinkedHashSet<String>();
            annotataions.add(MyAnnotation.class.getCanonicalName());
            return annotataions;
        }
    
        /**
         * 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6
         * @return  使用的Java版本
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    }
    

    注册Processor
    要让javac执行期间调用我们自定义的Processor,我们需要注册自定义的Processor:
    方法一:在main文件夹下创建resources/META-INF/javax.annotation.processing.Processor,在该文件中的内容是Processor的完整包名。
    方法二:使用Google提供的@AutoSerivce注解:
    引入依赖:

    dependencies {
        compile 'com.google.auto.service:auto-service:1.0-rc2'
    }
    

    使用@AutoService生成META-INF/services/javax.annotation.processing.Processor文件:

    @AutoService(Processor.class)
    public final class ButterKnifeProcessor extends AbstractProcessor {
    ...
    }
    

    ButterKnife中使用的注解就是典型的RetentionPolicy.CLASS,使用过程和配置就不介绍了,我们就简单看看它的实现原理原理。
    ButterKnife中使用最多的一个注解BindView

    @Retention(CLASS) 
    @Target(FIELD)
    public @interface BindView {
      @IdRes int value();
    }
    

    使用的时候是这样的:

        @BindView(R.id.viewpager)
        ViewPager viewpager;
        @Override
          public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.main_activity);
             ButterKnife.bind(this);
         }
    

    @BindView编译的时候会调用到其相关Processor。看下ButterKnifeProcessor的中的重要方法:
    ButterKnifeProcessor#process

      @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //扫描所有具有注解的类,然后根据这些类的信息生成BindingSet,最后生成以TypeElement为键,BindingSet为值的键值对。
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    //遍历bindingMap 取出每一个对象,生成对应Java文件
        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();
    //BindingSet里面的信息生成对应的JavaFile 
          JavaFile javaFile = binding.brewJava(sdk, debuggable);
          try {
    //通过writeTo(Filer filer)生成java源文件
            javaFile.writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }
        return false;
      }
    

    执行完process后对应的java文件就生成了。
    Activity运行的时候会执行到
    ButterKnife#bind(this);

      @NonNull @UiThread
      public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return createBinding(target, sourceView);
      }
    

    接着执行ButterKnife#createBinding

      private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
        Class<?> targetClass = target.getClass();
    //获取一个Unbinder子类的构造方法
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
    ...
    //执行构造方法
          return constructor.newInstance(target, source);
    ...
      }
    

    ButterKnife#createBindingc方法中首先拿到我们绑定的Activity的Class。
    然后通过ButterKnife#findBindingConstructorForClass获取一个Unbinder子类的构造方法。

      @Nullable @CheckResult @UiThread
      private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //先看有没有缓存
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        if (bindingCtor != null) {
          return bindingCtor;
        }
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
          return null;
        }
          Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
          //noinspection unchecked
          bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    ...
          bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    ...
    //设置缓存
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
      }
    
    ButterKnifeProcessor编译后生成的java文件

    直接看MainActivity_ViewBinding:

    public class MainActivity_ViewBinding implements Unbinder {
      private MainActivity target;
      @UiThread
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
      @UiThread
      public MainActivity_ViewBinding(MainActivity target, View source) {
        this.target = target;
    //1************************
        target.viewpager = Utils.findRequiredViewAsType(source, R.id.viewpager, "field 'viewpager'", ViewPager.class);
      }
      @Override
      @CallSuper
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
        target.viewpager = null;
        target.tabVideo = null;
        target.tabFind = null;
        target.tabMine = null;
        target.rg = null;
      }
    }
    

    //1**这样就明确了,执行构造方法的时候就把对应的 @BindView(R.id.viewpager)执行了findView方法。

    RetentionPolicy.RUNTIME

    运行期注解,定义的注解为@Retention为RUNTIME,能够通过运行时的反射机制来处理注解。
    ormlite数据库架包中将对象动态转成数据库表的过程使用的就是运行期注解。

    //表字段注解
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DatabaseTable {
        String tableName() default "";
        Class<?> daoClass() default Void.class;
    }
    

    使用@DatabaseTable(tableName=””)可指定表创建时的表名;

    @DatabaseTable(tableName = "user") // 指定数据表的名称
    public class UserBean {
    //表字段注解
        @DatabaseField(generatedId = true, columnName ="id", useGetSet = true)
        private int id;
        @DatabaseField(columnName ="name", useGetSet = true, canBeNull = false, unique = true)
    ...
    }
    

    创建表的时候调用
    TableUtils#createTable

    //Class对象传入的就是自定义对象的Class对象
    public static <T> int createTable(ConnectionSource connectionSource, Class<T> dataClass) throws SQLException {
    //获取一个向数据库读或写的一个类
            Dao<T, ?> dao = DaoManager.createDao(connectionSource, dataClass);
    //根据dao对象中从注解中获取的表数据,动态创建sql语句,然后创建表。
            return doCreateTable(dao, false);
        }
    

    DaoManager#createDao

        public synchronized static <D extends Dao<T, ?>, T> D createDao(ConnectionSource connectionSource, Class<T> clazz)
                throws SQLException {
            ...
    //获取类的注解
            DatabaseTable databaseTable = clazz.getAnnotation(DatabaseTable.class);
            if (databaseTable == null || databaseTable.daoClass() == Void.class
                    || databaseTable.daoClass() == BaseDaoImpl.class) {
                // see if the database type has some special table config extract method (Android)
                DatabaseType databaseType = connectionSource.getDatabaseType();
    //1**********
                DatabaseTableConfig<T> config = databaseType.extractDatabaseTableConfig(connectionSource, clazz);
                Dao<T, ?> daoTmp;
                if (config == null) {
                    daoTmp = BaseDaoImpl.createDao(connectionSource, clazz);
                } else {
                    daoTmp = BaseDaoImpl.createDao(connectionSource, config);
                }
                dao = daoTmp;
                       ...
            registerDao(connectionSource, dao);
            D castDao = (D) dao;
            return castDao;
        }
    

    //1*处执行
    SqliteAndroidDatabaseType#extractDatabaseTableConfig,提取类中注解的表配置

    @Override
        public <T> DatabaseTableConfig<T> extractDatabaseTableConfig(ConnectionSource connectionSource, Class<T> clazz)
                throws SQLException {
            return DatabaseTableConfigUtil.fromClass(connectionSource, clazz);
        }
    

    SqliteAndroidDatabaseType#extractDatabaseTableConfig中执行DatabaseTableConfigUtil#fromClass,在这个方法中会获取对象的注解,然后提取表名和表的字段。

        public static <T> DatabaseTableConfig<T> fromClass(ConnectionSource connectionSource, Class<T> clazz)
                throws SQLException {
            DatabaseType databaseType = connectionSource.getDatabaseType();
            String tableName = DatabaseTableConfig.extractTableName(databaseType, clazz);
            List<DatabaseFieldConfig> fieldConfigs = new ArrayList<DatabaseFieldConfig>();
            for (Class<?> classWalk = clazz; classWalk != null; classWalk = classWalk.getSuperclass()) {
                for (Field field : classWalk.getDeclaredFields()) {
                    DatabaseFieldConfig config = configFromField(databaseType, tableName, field);
                    if (config != null && config.isPersisted()) {
                        fieldConfigs.add(config);
                    }
                }
            }
            if (fieldConfigs.size() == 0) {
                return null;
            } else {
                return new DatabaseTableConfig<T>(clazz, tableName, fieldConfigs);
            }
        }
    

    参考:
    https://www.cnblogs.com/lyy-2016/p/6288535.html
    http://blog.csdn.net/bao19901210/article/details/17201173/
    http://blog.csdn.net/github_35180164/article/details/52121038
    http://blog.csdn.net/silenceoo/article/details/77897718

    相关文章

      网友评论

          本文标题:Android中的注解

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