Android开发Android开发Android技术知识

Android编译期插桩,让程序自己写代码

2019-07-31  本文已影响10人  2c3d4f7ba0d4

原文链接https://juejin.im/post/5ca31f6ae51d4509ab78d76a

前言

近些年,编译期插桩技术在Android圈越来越普遍。无论是可以生成JAVA源码的ButterKnief、Dagger,还是操作字节码的VirtualAPK,甚至是新兴的语言Kotlin都用到了编译期插桩技术。学习这门技术对我们理解这些框架的原理十分有帮助。另外,我们通过这种技术可以抽离出复杂、重复的代码,降低程序耦合性,提高代码的可复用性,提高开发效率。因此,了解编译期插桩技术十分必要。在介绍这项技术之前,我们先来了解一下Android代码的编译过程以及插桩位置。话不多说,直接上图。

APT

APT(Annotation Processing Tool)是一种编译期注解处理器。它通过定义注解和处理器来实现编译期生成代码的功能,并且将生成的代码和源代码一起编译成.class文件。

代表框架:ButterKnife、Dagger、ARouter、EventBus3、DataBinding、AndroidAnnotation等。

在介绍如何应用APT技术之前,我们先来了解一些相关的知识。

一、Element

1.简介

Element是一种在编译期描述.java文件静态结构的一种类型,它可能表示一个package、一个class、一个method或者一个field。Element的比较应该使用equals,因为编译期间同一个Element可能会用两个对象表示。JDK提供了以下5种Element

[图片上传失败...(image-de316e-1564558999211)]

<figcaption></figcaption>

2.Element的存储结构

编译器采用类似Html的Dom树来存储Element。我们用下面的Test.java来具体说明。

//PackageElement
package me.zhangkuo.compile;

//TypeElement
public class Test {

    //VariableElement
    private String name;

    //ExecutableElement
    private Test(){
    }

    //ExecutableElement
    public void setName(/* TypeParameterElement */ String name) {
        this.name = name;
    }
}

Test.java用Element树结构描述如下:

我们可以看到 setName(String name)ExecutableElement中并没有子节点TypeParameterElement。这是因为TypeParameterElement没有被纳入到Element树中。不过我们可以通过ExecutableElementgetTypeParameters()方法来获取。

此外,再给大家介绍两个Element中十分有用的方法。

public interface Element extends AnnotatedConstruct {
    //获取父Element
    Element getEnclosingElement();
    //获取子Element的集合
    List<? extends Element> getEnclosedElements();
}

二、TypeMirror

Element有一个asType()方法用来返回TypeMirrorTypeMirror表示 Java 编程语言中的类型。这些类型包括基本类型、声明类型(类和接口类型)、数组类型、类型变量和 null 类型。还可以表示通配符类型参数、executable 的签名和返回类型,以及对应于包和关键字 void 的伪类型。我们一般用TypeMirror进行类型判断。如下段代码,用来比较元素所描述的类型是否是Activity的子类。

/**
 * 类型相关工具类
 */
private Types typeUtils;
/**
 * 元素相关的工具类
 */
private Elements elementUtils;
private static final String ACTIVITY_TYPE = "android.app.Activity";

private boolean isSubActivity(Element element){
    //获取当前元素的TypeMirror
    TypeMirror elementTypeMirror = element.asType();
    //通过工具类Elements获取Activity的Element,并转换为TypeMirror
    TypeMirror viewTypeMirror = elementUtils.getTypeElement(ACTIVITY_TYPE).asType();
    //用工具类typeUtils判断两者间的关系
    return typeUtils.isSubtype(elementTypeMirror,viewTypeMirror)
}

三、一个简单的ButterKnife

这一节我们通过编写一个简单的ButterKnife来介绍一下如何编写一个APT框架。APT应该是编译期插桩最简单的一种技术,通过三步就可以完成。

  1. 定义编译期注解。

我们新增一个Java Library Module命名为apt_api,编写注解类BindView。

@Retention(RetentionPolicy.Class)
@Target(ElementType.FIELD)
public @interface BindView {
}

这里简单介绍一下RetentionPolicyRetentionPolicy是一个枚举,它的值有三种:SOURCE、CLASS、RUNTIME。

  1. 定义注解处理器。

同样,我们需要新增一个Java Library Module命名为apt_processor

我们需要引入两个必要的依赖:一个是我们新增的module apt_annotation,另一个是google的com.google.auto.service:auto-service:1.0-rc3(以下简称auto-service)。

implementation project(':apt_api')
api 'com.google.auto.service:auto-service:1.0-rc3'

新增一个类 ButterKnifeProcessor,继承 AbstractProcessor

@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {   
    /**
     * 元素相关的工具类
     */
    private Elements elementUtils;
    /**
     * 文件相关的工具类
     */
    private Filer filer;
    /**
     * 日志相关的工具类
     */
    private Messager messager;
    /**
     * 类型相关工具类
     */
    private Types typeUtils;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(BindView.class.getCanonicalName());
    }

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

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
        typeUtils = processingEnv.getTypeUtils();
    }

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

auto-service为我们简化了定义注解处理器的流程。@AutoService是就是由auto-service提供的,其作用是用来告诉编译器我们定义的ButterKnifeProcessor是一个编译期注解处理器。这样在编译时ButterKnifeProcessor才会被调用。

我们还重写了AbstractProcessor提供的四个方法:getSupportedAnnotationTypesgetSupportedSourceVersioninitprocess

上一篇下一篇

猜你喜欢

热点阅读