Android APT案例

APT(Annotation Processing Tool)即注解处理器,是一种注解处理的工具,用来在编译器扫描以及处理注解。以注解作为桥梁,通过预先设定好的代码规则来生成对应的Java代码。实际开发中有很多开源框架都是用到APT,例如:ButterKnife,Dagger2,EventBus,Spring框架等。

APT原理:在注解了某些代码元素(字段、函数、类)后,编译器检查AbstractProcessor的子类,自动调用process()方法,将所有添加了指定注解的元素传递给该函数,在根据这些元素通过Java规则生成对应的Java代码。

现在我就简单的实现一个仿ButterKnife的功能。

实现ButterKnife中绑定View的功能

我们先观察在ButterKnife中生成的注解绑定的代码,寻找其规律后仿造即可:

package com.wdl.aptdemo;

public class MainActivityViewBinding {
    public static void bind(MainActivity _mainActivity) {
        _mainActivity.btn = (android.widget.Button) (_mainActivity.findViewById(2131165218));
        _mainActivity.textView = (android.widget.TextView) (_mainActivity.findViewById(2131165326));
    }
}

规律:依照规律指定Java生成规则

  • 生成的Java文件与对应的Activity在同一个包下
  • 类名Source+ViewBinding
  • 方法名为bind,参数为Source _source静态方法
  • _source.fieldName = (Type)(_source.findViewById(value));
实现的步骤:
  1. 创建名为apt_annotation的Java Library(Moudle),用于存放定义的注解(创建过程省略)
  2. 创建名为apt_processor的Java Library(Moudle),并引用apt_annotation,用于处理注解以及生成Java代码
    apt_processor下build.gradle中如下依赖:(看注释)
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 注解注册处理器
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    // square 开源的代码生成框架,通过API生成指定格式(修饰符 返回值 参数 函数体)的代码
    implementation 'com.squareup:javapoet:1.10.0'
    implementation project(':apt_annotation')
}

sourceCompatibility = "7"
targetCompatibility = "7"
  1. app/build.gradle中添加依赖:
implementation project(":apt_annotation")
annotationProcessor project(":apt_processor")
  1. 编写注解以及apt_processor中的具体逻辑
    在apt_annotation中编写我们要使用的注解类:
package com.wdl.apt_annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@SuppressWarnings("unused")
// 保留到编译阶段不加载进JVM
@Retention(RetentionPolicy.CLASS)
// 作用对象,FILED
@Target(ElementType.FIELD)
public @interface BindView {
    int value();  // 属性名为value 的int变量
}

AbstractProcessor子类的实现:

package com.wdl.apt_processor;


import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.wdl.apt_annotation.BindView;

import java.io.IOException;
import java.text.MessageFormat;
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.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.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;

@SuppressWarnings("unused")
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
    // Element代表程序的元素,例如包、类、方法。
    private Elements elementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
    }

    /**
     * @return 指定java版本。
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 指定该目标的注解对象,指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。
     *
     * @return Set getCanonicalName即包名.类名,不同的对象获取的值不同,可能为空
     */
    @Override
    public Set getSupportedAnnotationTypes() {
        Set hashSet = new HashSet<>();
        hashSet.add(BindView.class.getCanonicalName());
        return hashSet;
    }

    /**
     * 处理包含指定注解对象的代码元素
     * 获取控件变量的引用以及对应的viewId,先遍历出每个Activity所包含的所有注解对象
     *
     * @param set              Set
     * @param roundEnvironment RoundEnvironment 所有注解的集合
     * @return true
     */
    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        // 获取所有包含BindView注解的元素
        Set elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        // 此处的TypeElement就是Activity
        // Activity中包含的 id以及对应的属性(控件)
        Map> typeElementMapHashMap = new HashMap<>();
        for (Element element : elementSet) {
            // 注解的是FIELD,因此可以直接转换
            VariableElement variableElement = (VariableElement) element;
            // 获取最里层的元素,此处就是Activity
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            // 获取对应Activity中的Map viewId View
            Map variableElementMap = typeElementMapHashMap.get(typeElement);
            if (variableElementMap == null) {
                variableElementMap = new HashMap<>();
                typeElementMapHashMap.put(typeElement, variableElementMap);
            }
            // 获取注解对象
            BindView bindView = variableElement.getAnnotation(BindView.class);
            // 获取注解值
            int id = bindView.value();
            variableElementMap.put(id, variableElement);
        }

        for (TypeElement key : typeElementMapHashMap.keySet()) {
            Map elementMap = typeElementMapHashMap.get(key);
            String packageName = elementUtils.getPackageOf(key).getQualifiedName().toString();

            JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key,
                    elementMap)).build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return true;
    }

    private TypeSpec generateCodeByPoet(TypeElement typeElement, Map variableElementMap) {
        //自动生成的文件以 Activity名 + ViewBinding 进行命名
        return TypeSpec.classBuilder(typeElement.getSimpleName().toString() + "ViewBinding")
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethodByPoet(typeElement, variableElementMap))
                .build();
    }

    /**
     * @param typeElement        注解对象的根元素,即Activity
     * @param variableElementMap Activity包含的注解对象以及注解的目标对象
     * @return MethodSpec
     */
    private MethodSpec generateMethodByPoet(TypeElement typeElement, Map variableElementMap) {
        ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
        //  _mainActivity.btn_serializeSingle = (android.widget.Button) (_mainActivity.findViewById(2131165221));
        // 第一个转小写+下划线
        String parameter = "_" + Utils.toLowerCaseFirstChar(className.simpleName());
        MethodSpec.Builder builder = MethodSpec.methodBuilder("bind") // 方法名
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)             // public static
                .returns(void.class)// 返回类型
                .addParameter(className, parameter);
        for (int viewId : variableElementMap.keySet()) {
            VariableElement variableElement = variableElementMap.get(viewId);
            // 变量名
            String fieldName = variableElement.getSimpleName().toString();
            // 变量父类的全称
            String fieldType = variableElement.asType().toString();
            String text = "{0}.{1} = ({2})({3}.findViewById({4}));";
            builder.addCode(MessageFormat.format(text, parameter, fieldName, fieldType, parameter, String.valueOf(viewId)));
        }
        return builder.build();
    }
}


详细方法:

  • getSupportedAnnotationTypes():返回该类指定注解的对象
  • generateCodeByPoet():生成对应的类
  • generateMethodByPoet:生成对应方法

最终项目的整体架构如下:
Android APT案例_第1张图片

重新Build项目后,在APP下会生成对应的代码:
Android APT案例_第2张图片

如何使用?与ButterKnife类似。

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn_btn)
    Button btn;
    @BindView(R.id.tv_t)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainActivityViewBinding.bind(this);
        //textView.setText("Succeed.....................");
    }
}

我的Github,大家Start

参考:https://www.jianshu.com/p/cc8379522c5e

你可能感兴趣的:(android)