安卓自定义注解基础(一)

在这里插入图片描述
学习博客:https://juejin.cn/post/6844903437834911757

1.概念篇

什么是注解

先来看看Java文档中的定义

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

注解是一种元数据, 可以添加到java代码中. 方法变量参数都可以被注解,注解对注解的代码没有直接影响.

TYPE:接口枚举注解类型

首先, 明确一点: 注解并没有什么魔法, 之所以产生作用, 是对其解析后做了相应的处理. 注解仅仅只是个标记罢了.

定义注解用的关键字是@interface

元注解

java内置的注解有Override,Deprecated, SuppressWarnings等, 作用相信大家都知道.

现在查看Override注解的源码

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

发现Override注解上面有两个注解, 这就是元注解. 元注解就是用来定义注解的注解.其作用就是定义注解的作用范围, 使用在什么元素上等等, 下面来详细介绍.

元注解共有四种@Retention, @Target, @Inherited, @Documented

  • @Retention 保留的范围,默认值为CLASS. 可选值有三种

    • SOURCE, 只在源码中可用

      注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于做一些检查性的操作,比如 @Override@SuppressWarnings

    • CLASS, 在源码和字节码中可用

      注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife

    • RUNTIME, 在源码,字节码,运行时均可用

      注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;用于在运行时去动态获取注解信息。这个注解大都会与反射一起使用

  • @Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER等,未标注则表示可修饰所有

  • @Inherited 是否可以被继承,默认为false

  • @Documented 是否会保存到 Javadoc 文档中

其中, @Retention是定义保留策略, 直接决定了我们用何种方式解析. SOUCE级别的注解是用来标记的, 比如Override, SuppressWarnings. 我们真正使用的类型是CLASS(编译时)和RUNTIME(运行时)

自定义注解

结合例子讲解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestAnnotation {
    String value();
    String[] value2() default "value2";
}

元注解的的意义参考上面的讲解, 不再重复, 这里看注解值的写法:

类型 参数名() default 默认值;

其中默认值是可选的, 可以定义, 也可以不定义.

处理运行时注解

Retention的值为RUNTIME时, 注解会保留到运行时, 因此使用反射来解析注解.

使用的注解就是上一步的@TestAnnotation, 解析示例如下:

public class Demo {

    @TestAnnotation("Hello Annotation!")
    private String testAnnotation;

    public static void main(String[] args) {
        try {
            // 获取要解析的类
            Class cls = Class.forName("myAnnotation.Demo");
            // 拿到所有Field
            Field[] declaredFields = cls.getDeclaredFields();
            for(Field field : declaredFields){
                // 获取Field上的注解
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                if(annotation != null){
                    // 获取注解值
                    String value = annotation.value();
                    System.out.println(value);
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

解析编译时注解

解析编译时注解需要继承AbstractProcessor类, 实现其抽象方法

public boolean process(Set annotations, RoundEnvironment roundEnv)

该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理.

处理示例:

// 指定要解析的注解
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")
// 指定JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (TypeElement te : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
                TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
                // do something
            }
        }
        return true;
    }
}

2.自定义注解之 BindView

可能遇到问题AutoService注解无法生成META-INF文件

1.创建java-Libary

1.创建注解接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSMq1wqr-1635763553810)(image-20210218111510171.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vefRATzA-1635763553811)(image-20210218111532093.png)]

2.注解绑定

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRrzI4UT-1635763553813)(image-20210218111645627.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oadCbAPp-1635763553814)(image-20210218111709581.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZuIZ9uN-1635763553815)(image-20210218111730351.png)]

3.编写生成注解代码

package com.tsp.apt_process;

import com.tsp.apt_annotation.BindView;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;

/**
 * https://blog.csdn.net/cpcpcp123/article/details/103871815
 * 需要手动新建目录和相应的文件
 */

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.tsp.apt_annotation.BindView")
public class BindViewProcessor extends AbstractProcessor {

    private Filer mFilerUtils;       // 文件管理工具类
    private Types mTypesUtils;    // 类型处理工具类
    private Elements mElementsUtils;  // Element处理工具类
    private Map<TypeElement, Set<ViewInfo>> mToBindMap = new HashMap<>(); //用于记录需要绑定的View的名称和对应的id


    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFilerUtils = processingEnv.getFiler();
        mTypesUtils = processingEnv.getTypeUtils();
        mElementsUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        System.out.println("start process");
        if (set != null && set.size() != 0) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获得被BindView注解标记的element

            categories(elements);//对不同的Activity进行分类

            //对不同的Activity生成不同的帮助类
            for (TypeElement typeElement : mToBindMap.keySet()) {
                String code = generateCode(typeElement);    //获取要生成的帮助类中的所有代码
                String helperClassName = typeElement.getQualifiedName() + "$$Autobind"; //构建要生成的帮助类的类名
                //输出帮助类的java文件,在这个例子中就是MainActivity$$Autobind.java文件
                //输出的文件在build->source->apt->目录下
                try {
                    JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
                    Writer writer = jfo.openWriter();
                    writer.write(code);
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }


    private void categories(Set<? extends Element> elements) {
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;    //被@BindView标注的应当是变量,这里简单的强制类型转换
            TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement(); //获取代表Activity的TypeElement
            Set<ViewInfo> views = mToBindMap.get(enclosingElement); //views储存着一个Activity中将要绑定的view的信息
            if (views == null) {    //如果views不存在就new一个
                views = new HashSet<>();
                mToBindMap.put(enclosingElement, views);
            }
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class);    //获取到一个变量的注解
            int id = bindAnnotation.value();    //取出注解中的value值,这个值就是这个view要绑定的xml中的id
            views.add(new ViewInfo(variableElement.getSimpleName().toString(), id));    //把要绑定的View的信息存进views中
        }
    }

    /**
     * 字符串拼接 生成代码
     *
     * @param typeElement
     * @return
     */
    private String generateCode(TypeElement typeElement) {
        //1.获取需要绑定的view所在的类名称
        String rawClassName = typeElement.getSimpleName().toString();
        //2.获取需要绑定的view所在的包名
        String packageName = mElementsUtils.getPackageOf(typeElement).getQualifiedName().toString();
        //3.生成需要帮助的类名称
        String helperClassName = rawClassName + "$$Autobind";
        //4.生成代码
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(packageName).append(";\n");   //构建定义包的代码
        sb.append("import com.tsp.apt_api.template.IBindHelper;\n\n");//导包
        sb.append("public class ").append(helperClassName).append(" implements ").append("IBindHelper");   //构建定义帮助类的代码
        sb.append(" {\n"); //代码格式,可以忽略
        sb.append("\t@Override\n");    //声明这个方法为重写IBindHelper中的方法
        sb.append("\tpublic void inject(" + "Object" + " target ) {\n");   //构建方法的代码
        for (ViewInfo viewInfo : mToBindMap.get(typeElement)) { //遍历每一个需要绑定的view
            sb.append("\t\t"); //代码格式,可以忽略
            sb.append(rawClassName + " substitute = " + "(" + rawClassName + ")" + "target;\n");    //强制类型转换
            sb.append("\t\t"); //代码格式,可以忽略
            sb.append("substitute." + viewInfo.viewName).append(" = ");    //构建赋值表达式
            sb.append("substitute.findViewById(" + viewInfo.id + ");\n");  //构建赋值表达式
        }
        sb.append("\t}\n");    //代码格式,可以忽略
        sb.append('\n');   //代码格式,可以忽略
        sb.append("}\n");  //代码格式,可以忽略
        return sb.toString();
    }


    //要绑定的View的信息载体
    class ViewInfo {
        String viewName;    //view的变量名
        int id; //xml中的id

        public ViewInfo(String viewName, int id) {
            this.viewName = viewName;
            this.id = id;
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9QBvzl2-1635763553816)(image-20210218111907376.png)]
需要生成resources文件夹下的services文件,才能在编译的时候生成对应的java代码

4.编译生成的代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhQ9TZ8L-1635763553817)(image-20210218112056108.png)]

5.在Activity中应用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I58FPDLS-1635763553818)(image-20210218112127609.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值