Android 之注解、APT技术

注解的作用

  1. 注解+ APT 技术,生成 Java 文件。如DataBinding、Hilt、ButterKnife 等框架
  2. 注解+反射+动态代码
  3. 注解定义常量(代替枚举)或者限制函数里参数的范围(@DrawableRes、@StringRes等)

代替枚举

Java 代码

@IntDef({JavaSwitch.DISABLE, JavaSwitch.ENABLE})
@Target(ElementType.PARAMETER)
@Retention(AnnotationRetention.SOURCE)
public @interface JavaSwitch {
    
    int DISABLE = 0;
    int ENABLE = 1;
    
}

Kotlin 代码

@IntDef(KotlinSwitch.DISABLE, KotlinSwitch.ENABLE)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.SOURCE)
annotation class KotlinSwitch {
    companion object {
        const val DISABLE = 0
        const val ENABLE = 1
    }
}

具体使用:

但是该方案嵌套方法/函数调用时。as 不会报错

APT技术

这才是注解的『最终归宿』,全称 Annotation Processing Tool 即注解处理器。简单来讲:在编译期识别自定义的注解然后根据规则生成对应代码。如:ButterKnife
现在利用 APT 技术来实现 ButterKnife 的 BindView 功能(自动FindViewById)
● 创建一个 Java Module 名为 apt-test-annotation
● 创建一个 Java Module 名为 apt-test-processor
● 创建一个 Android Module 名为 apt-test-api

@Target(ElementType.FIELD)//能修饰参数
@Retention(RetentionPolicy.CLASS)//表示注解只在源码中存在,编译成 class 之后,就没了
public @interface BindView {
    
    int value();
    
}

在 apt-test-annotation 的 gradle 文件里需要引入

// 不引用无法执行注解执行器
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
// 以及需要引用 apt-test-processor。因为注解处理需需要使用这个注解
implementation project(path: ":apt-test-annotation")

重点是 AptTestAnnotationProcessor这个类

@AutoService(Processor.class)
public class AptTestAnnotationProcessor extends AbstractProcessor {
    
    /**
    * 文件生成器
    */
    private Filer mFiler;
    
    /**
    * 日志
    */
    private Messager mMessager;
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
    }
    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, "注解处理器运行了");
        
        if (set == null || set.isEmpty()) {
            return false;
        }
        // 读取 BindView 注解
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);
        // 存储每个Activity中的所有注解属性
        HashMap<String, List<VariableElement>> annotatedMap = new HashMap<>();
        
        if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) {
            for (Element element : elementsAnnotatedWith) {
                // 转成属性节点
                VariableElement e = (VariableElement) element;
                // 获取上一个节点,也就是类名
                TypeElement typeElement = (TypeElement) e.getEnclosingElement();
                // 获取类名
                String activityName = typeElement.getSimpleName().toString();
                //缓存对应类的所有节点
                List<VariableElement> variableElementList = annotatedMap.get(activityName);
                if (variableElementList == null) {
                    variableElementList = new ArrayList<>();
                    annotatedMap.put(activityName, variableElementList);
                }
                // 缓存所有的属性注解节点
                variableElementList.add(e);
            }
        }
        if (annotatedMap.size() > 0) {
            Writer writer = null;
            for (String activityName : annotatedMap.keySet()) {
                // 得到类上的所有节点
                List<VariableElement> variableElements = annotatedMap.get(activityName);
                //得到包名
                String packageName = processingEnv.getElementUtils().getPackageOf(variableElements.get(0)).toString();
                // 自定义类名,仿造 Butterknife
                String newClass = activityName + "_ViewBinding";
                try {
                    // 创建java文件
                    JavaFileObject javaFile = mFiler.createSourceFile(packageName + "." + newClass);
                    // 写入代码
                    writer = javaFile.openWriter();
                    StringBuilder buffer = new StringBuilder();
                    // 拼接代码
                    buffer.append("package ").append(packageName).append(";\n");
                    buffer.append("import android.view.View;\n");
                    buffer.append("public class ").append(newClass).append("{\n");
                    buffer.append("public ").append(newClass).append("(final ").append(packageName).append(".").append(activityName).append(" target){\n");
                    for (VariableElement element : variableElements) {
                        String fieldName = element.getSimpleName().toString();
                        int resId = element.getAnnotation(BindView.class).value();
                        // 执行findViewById
                        buffer.append("target.").append(fieldName).append("=target.findViewById(").append(resId).append(");\n");
                    }
                    buffer.append("}\n}");
                    writer.write(buffer.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (writer != null) {
                            writer.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return true;
    }
    
    /**
    * 当前注解处理器支持的注解集合,如果支持,就会调用 process 方法
    *
    * @return 支持的注解集合
    */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }
    
    /**
    * 编译当前注解处理器的 JDK 版本
    *
    * @return JDK 版本
    */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    
}

编译项目就会生成代码

生成代码其实是靠 Filer 这个类,但是代码里的方式不太灵活。可以使用 JavaPoet 库来帮助生成代码
PS:在主模块别忘了引用三个 Module

来自:https://www.yuque.com/tongsr/xy0i84/xzl5iq

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值