Android 架构师之路 APT (1)

作者: zidea | 来源:发表于2020-05-05 20:17 被阅读0次
Android-Developers.jpg

今天我们先给出 demo 的代码,随后会就 APT 技术进行详细介绍

APT

目标通过自己视图绑定功能来了解如何 APT 帮助我们在编译期间自动生成代码。在网上收集一些资料自己实现一下。

  • 目标使用 APT 实现类似 Butter Knife 库提供的通过注解实现视图绑定的功能
    最近使用 binding 实现视图绑定功能。有点渐渐远离了 ButterKnife ,不过这个库还是给我留下美好的回忆,简洁明了的 API 让人印象深刻。

创建一个空 Android 项目,接下我们添加模块到项目,每一个模块在 APT 中实现不同功能。

创建模块

  • 创建 zi-annotation 注意这是一个 (java 模块),在这个模块中,可以创建注解 ZiViewBind


    apt_create_java_module.JPG
  • 创建 zi-api (android 模块)提供用户可以调用 api ,这里实现 view 的绑定。


    apt_create_android_module.JPG
  • 创建 zi-compiler(java 模块) 编写注解处理 。
No. 模块名 模块类型 说明
1 zi-annotation java 模块 在这个模块中,可以创建注解
2 zi-api Android 模块 提供用户可以调用 api
3 zi-compiler java 模块 编写注解处理

Android Studio 在当我们创建好模块自动在 setting 文件中写入了这些模块

rootProject.name='zi application'
include ':app'
include ':zi-annotation'
include ':zi-api'
include ':zi-compiler'

处理依赖关系

  • build.gradle(zi-compiler)
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.squareup:javapoet:1.10.0'
    implementation project(':zi-annotation')
}

sourceCompatibility = "7"
targetCompatibility = "7"

  • 注意 zi-compiler 依赖于 zi-annotation 模块
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.10.0'

在主项目中添加刚刚创建好的几个模块,值得注意 zi-compiler 为 annotationProcessor

dependencies {
    ...

    implementation project(':zi-annotation')
    implementation project(':zi-api')
    annotationProcessor project(':zi-compiler')
}

到此我们已经完成模块创建、添加以及他们之间依赖关系,剩下的工作就是为这些模块添加代码。


project_architeure.JPG

zi-annotation 模块

创建 ZiBindView 注解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ZiBindView {
}

@Target说明了Annotation所修饰的对象范围

说明
CONSTRUCTOR 用于描述构造器
FIELD 用于描述域
LOCAL_VARIABLE 用于描述局部变量
METHOD 用于描述方法
PACKAGE 用于描述包
PARAMETER 用于描述参数
TYPE 用于描述类、接口(包括注解类型) 或enum声明

zi-compiler 模块

创建 ZiBindViewProcessor 和 ClassCreatorProxy
在代码中不涉及的注解这里就不解释了

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

注解处理器需要继承于 AbstractProcessor ,其中部分代码写法基本是固定的。

public class ZiBindViewProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}

getSupportedAnnotationTypes() 返回支持的注解类型
getSupportedSourceVersion() 返回支持的源码版本

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        //获取类名
        supportTypes.add(ZiBindView.class.getCanonicalName());
        return supportTypes;
    }

这里,getName()返回的是虚拟机里面的class的表示,而getCanonicalName()返回的是更容易理解的表示。其实对于大部分class来说这两个方法没有什么不同的。但是对于array或内部类来说是有区别的。
另外,类加载(虚拟机加载)的时候需要类的名字是getName。

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

收集信息

谓信息收集,就是根据我们声明,得到对应 Element,然后注解进行收集所需的信息,这些信息用于后期生产对象

生产代理类(在编译时,将文本生产为类的生产代理类 ClassCreatorProxy

针对每一个都会生产一个注解类

初始化

public interface ProcessingEnvironment {
    Elements getElementUtils();
    Types getTypeUtils();
    Filer getFiler();
    Locale getLocale();
    Messager getMessager();
    Map<String, String> getOptions();
    SourceVersion getSourceVersion();
    
}

ZiBindViewProcessor 完整代码

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({"zidea.example.com.zi_compiler.ZiBindViewProcessor"})
@AutoService(Processor.class)
public class ZiBindViewProcessor extends AbstractProcessor {

    //跟日志相关的辅助类
    private Messager mMessager;
    //跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。
    private Elements mElementUtils;
    private Map<String,ClassCreatorProxy> mProxyMap = new HashMap<>();

    //利用 ProcessingEnvironment 对象获取 Elements
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        //获取类名
        supportTypes.add(ZiBindView.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    // 收集信息
    //  所谓信息收集,就是根据我们声明,得到对应 Element,然后注解进行收集所需的信息,这些信息用于后期生产对象
    // 生产代理类(在编译时,将文本生产为类的生产代理类 ClassCreatorProxy,针对每一个都会生产一个注解类
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //输出信息
        mMessager.printMessage(Diagnostic.Kind.NOTE,"processing...");

        //根据注解获取所需信息

        //用来获取,注解所修饰的Element对象,getElementsAnnotatedWith 用于获取注解 ZiBindView 的 Element 对象
        Set<? extends Element> elements =roundEnvironment.getElementsAnnotatedWith(ZiBindView.class);

        //遍历说有在类中,添加了 ZiBindView 类
        for (Element element : elements){

            VariableElement variableElement = (VariableElement) element;
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            String fullClassName = classElement.getQualifiedName().toString();

            ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
            if(proxy == null){
                proxy = new ClassCreatorProxy(mElementUtils,classElement);
                mProxyMap.put(fullClassName,proxy);
            }
            ZiBindView bindAnnotation = variableElement.getAnnotation(ZiBindView.class);
            int id = bindAnnotation.value();
            proxy.putElement(id,variableElement);

        }

        for(String key:mProxyMap.keySet()){
            ClassCreatorProxy proxyInfo = mProxyMap.get(key);
            JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build();

            try {
                javaFile.writeTo(processingEnv.getFiler());
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        mMessager.printMessage(Diagnostic.Kind.NOTE,"process finish ...");
        return true;
    }
}

        for(String key:mProxyMap.keySet()){
            ClassCreatorProxy proxyInfo = mProxyMap.get(key);
            JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build();

            try {
                javaFile.writeTo(processingEnv.getFiler());
            }catch (IOException e){
                e.printStackTrace();
            }
        }
  • 返回用来创建新的类的 filer

ClassCreatorProxy 完整代码

public class ClassCreatorProxy {
    //绑定的类名称
    private String mBindingClassName;
    //绑定的包名称
    private String mPackageName;
    private TypeElement mTypeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public ClassCreatorProxy(Elements elementUtils, TypeElement classElement){
        this.mTypeElement = classElement;

        PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
        String packageName = packageElement.getQualifiedName().toString();
        String className = mTypeElement.getSimpleName().toString();
        this.mPackageName = packageName;
        this.mBindingClassName = className + "_ViewBinding";
    }

    public void putElement(int id, VariableElement element){
        mVariableElementMap.put(id,element);
    }

    public String generateJavaCode(){
        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(mPackageName).append(";\n\n");
        builder.append("import com.example.gavin.apt_library.*;\n");
        builder.append('\n');
        builder.append("public class ").append(mBindingClassName);
        builder.append(" {\n");

        generateMethods(builder);
        builder.append('\n');
        builder.append("}\n");
        return builder.toString();
    }


    private void generateMethods(StringBuilder builder) {
        builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n");
        for (int id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            builder.append("host." + name).append(" = ");
            builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n");
        }
        builder.append("  }\n");
    }

    public String getProxyClassFullName() {
        return mPackageName + "." + mBindingClassName;
    }

    public TypeElement getTypeElement() {
        return mTypeElement;
    }
//JavaPoet是提供于自动生成java文件的构建工具类框架,使用该框架可以方便根据我们所注解的内容在编译时进行代码构建。
    public TypeSpec generateJavaCode2() {
        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethods2())
                .build();
        return bindingClass;

    }

    private MethodSpec generateMethods2() {
        ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(host, "host");

        for (int id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
        }
        return methodBuilder.build();
    }

    public String getPackageName(){
        return mPackageName;
    }
}

zi-api 模块

public class ZiBindViewTools {

    public static void bind(Activity activity) {

        Class clazz = activity.getClass();
        try {
            Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method method = bindViewClass.getMethod("bind", activity.getClass());
            method.invoke(bindViewClass.newInstance(), activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);

我们通过

### 使用 Api 
我们已经完成 APT 部分代码,那么如何使用我们创建好 ZiBindView 呢?具体使用方法和 butterKnife 基本一样。
```java
public class MainActivity extends AppCompatActivity {

    @ZiBindView(value = R.id.main_activity_textView)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ZiBindViewTools.bind(this);
        textView.setText("Zidea");
    }
}
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

但是当我们运行时候可能还无法生存代码,这是因为使用 gradle 版本过高,需要我们自己手动创建,

  • 首先将目录从 Android 切换到 Project 视图
  • zi-compiler目录下创建如图结构resources/META-INF/services文件目录
  • 然后在目录下添加一个问题file 即可,在 file中添加如下内容
    001.JPG
    也就是我们刚刚创建 ZiBindViewProcessor文件包名加文件名
    002.JPG

相关文章

网友评论

    本文标题:Android 架构师之路 APT (1)

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