Android APTAndroid Other

Android 学习(四):Android APT/Androi

2021-09-27  本文已影响0人  翟小乙

上一篇 Android 学习(三):Java 注解

Android APT学习

  1. 编译时技术作用生成模板代码
  2. 什么是编译时技术?


    6.png
1.0 学习目标
2.0 重点知识点
3.0 上代码学习
  1. 创建android项目
  2. 项目内创建注解的module,选择javalibrary ,名称为annotations
    注解Moudle.jpg
  3. 项目内创建注解处理器的module,选择 javalibrary,名称为annotations_compiler
    注解处理器Module.jpg
  4. 在项目/app/build.gradle下引入这个俩个moudle

正常引入module都为implementation因为annotations_compiler注解处理器所以改为 annotationProcessor

引入Module.jpg
  1. 在Module annotations注解中创建BindView 注解,用来传递控件ID(R.id.xxx)
    BindView.jpg
package com.zyj.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 定义注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
  1. 在Module annotations_compiler注解处理器 中创建AnnotationsCompiler,解析各个界面注解信息
    z5.jpg
  1. 初始化Filer ,作用:编译时获取到注解控件ID 写入文件就是把findViewById(R.id.xxx)写入文件。
  2. 设置注解处理器处理范围,只解读BindView 注解。
  3. 以下是process方法里面思路:
  4. 获取使用注解的所有Activity的节点。
    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class)
  5. 把所有节点进行分类,Activity和其成员变量归为一类,存入map中。
    VariableElement Field 成员变量 、TypeElement class 类 、 PackageElement 包 、 等可查看Element 子类
  6. 分好类,存入map后,通过filer创建java文件,生成代码。
    **重点看注释
package com.zyj.anntotations_compiler;

import com.google.auto.service.AutoService;
import com.zyj.annotations.BindView;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * 注解处理器   用来生成代码
 * <p>
 * 1. 注解处理器一定要继承一个抽象类  AbstractProcessor (AbstractProcessor 属于javax)
 * 2。需要依赖于谷歌服务库(这样注解就会在这边自动处理)
 * annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
 * compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
 * 3.依赖使用注解 @AutoService(Processor.class) 代表这个类就是一个注解处理器的类
 */
@AutoService(Processor.class)
public class AnnotationsCompiler extends AbstractProcessor  {
    //  注意: 我们调试的时候需要打断点或者打印日志,而在处理器里面不起作用,所以我们通过 Messager去操作
    Filer filer;
    Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        filer = processingEnvironment.getFiler();// 文件
        messager = processingEnvironment.getMessager();// 日志
        messager.printMessage(Diagnostic.Kind.WARNING, "我们开始可以看日志了!日志类型是警告!");

    }

    /**
     * 因为我们注解处理器不需要都要去处理,
     * 所以这个方法是声明注解处理器要处理的注解
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        // 1. String 类型里面可以添加包名类名,就能去筛选了
        // 2. 注解处理器模块需要依赖注解模块
        // 3. 这就说明这个注解处理器要处理的注解就是我们声明的这个注解BindView
        types.add(BindView.class.getCanonicalName());
        return types;
    }
    /**
     * 1. 方法一 :实现该方法
     * 2. 方法二:在该类处理器加注解  @SupportedSourceVersion()
     * 必须要有一个版本声明
     * 声明支持的java 版本
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    // 该方法专门用来搜索注解的方法
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //  生成代码
        // 去搜索出用到了BindView注解的节点

        // 如果有多个Activity 则会有多个Element 元素,可通过上下文 activity.findViewById()获取节点元素
        // VariableElement Field 成员变量    TypeElement class 类     PackageElement 包   等等都是节点
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);// 根据注解获取节点
        // 把每个Activity 和它里面的内容放到一起
        HashMap<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            // 获取这个成员变量所有在的类的类名
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//获取成员变量的上一个节点===》就是这个类
            typeElement.getQualifiedName().toString();// 获取类名--- 带包名
            String className = typeElement.getSimpleName().toString();// 获取类名----不带包名
            List<VariableElement> variableElements = map.get(className);// 通过类名 获取 成员变量
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(className, variableElements);
            }
            variableElements.add(variableElement);
        }
        // 生成代码
        if (map.size()>0){
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()){
                String className = iterator.next();
                List<VariableElement> variableElements = map.get(className);
                // 获取包名
                String packName = getPackName(variableElements.get(0));
                // 创建一个类名
                String newName = className+"_ViewBinder";
                try {
                    // 创建 java 文件
                    JavaFileObject sourceFile = filer.createSourceFile(packName + "." + newName);
                    writer = sourceFile.openWriter();
                    StringBuffer stringBuffer = new StringBuffer();
                    stringBuffer.append("package "+ packName+";\n");
                    stringBuffer.append("import android.view.View ;\n");
                    stringBuffer.append("public class "+ newName +" implements IButterKnifer<"+packName+"."+className+">{\n");
                    stringBuffer.append("public void bind("+packName+"."+className+" target){\n");
                    for (VariableElement variableElement :variableElements) {
                        // 遍历成员变量,每一个变量都生成findViewById代码
                        // 获取成员变量名字
                        String variableName = variableElement.getSimpleName().toString();
                        //  获取到上面的注解所持有的value redID
                        int resID = variableElement.getAnnotation(BindView.class).value();
                        stringBuffer.append("target."+variableName+"=target.findViewById("+resID+");\n");
                    }
                    stringBuffer.append("}\n}\n");
                    writer.write(stringBuffer.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    try {
                        if (writer!=null){
                            writer.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }

    /**
     * 根据成员变量获取包名
     * @param variableElement
     * @return
     */
    private String getPackName(VariableElement variableElement) {
        Element enclosingElement = variableElement.getEnclosingElement();// 获取上一级元素
        PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(enclosingElement);// 获取包节点

        String  packName = packageOf.getQualifiedName().toString();// 获取到包名

         return packName;


    }
}

  1. annotations_compiler注解处理器引入注解module和谷歌库

因为处理器annotations_compiler需要解读注解annotations,所以需要引入module,而引入谷歌库就是为了自动调用该注解处理器类


引入库.jpg
  1. 在项目app中新建包为apt,在其中创建IButterKnifer接口,AptActivity和IButterKnife类
    apt包.jpg

public interface IButterKnifer<T> {
    void bind(T target);
}

传递上下文获取当前Activity内控件ID


public class IButterKnife {
    public static void bind(Object target){
        String name = target.getClass().getName()+"_ViewBinder";;
        try {
            Class<?> aClass = Class.forName(name);
            if (IButterKnifer.class.isAssignableFrom(aClass)){
                // 判断 aClass 是不是 IButterKnifer类或子类
                IButterKnifer iButterKnifer = (IButterKnifer) aClass.newInstance();
                iButterKnifer.bind(target);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}
import android.os.Bundle;
import android.widget.TextView;
import com.zyj.annotations.BindView;
import com.zyj.obslove.R;
public class AptActivity extends AppCompatActivity {
    @BindView(R.id.text1)
    TextView text1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_apt);
        IButterKnife.bind(this);
        text1.setText("---------------------");
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".apt.AptActivity">
    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="text1"/>
</LinearLayout>
上一篇下一篇

猜你喜欢

热点阅读