Android编译期插桩,让程序自己写代码(一),跳槽面试大厂被拒

47 阅读4分钟

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。

  • SOURCE:不参与编译,让开发者使用。
  • CLASS:参与编译,运行时不可见。给编译器使用。
  • RUNTIME:参与编译,运行时可见。给编译器和JVM使用。
  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 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

  • getSupportedAnnotationTypes表示处理器可以处理哪些注解。这里返回的是我们之前定义的BindView。除了重写方法之外,还可用通过注解来实现。

@SupportedAnnotationTypes(value = {"me.zhangkuo.apt.annotation.BindView"})

  • getSupportedSourceVersion表示处理器可以处理的Java版本。这里我们采用最新的JDK版本就可以了。同样,我们也可以通过注解来实现。

@SupportedSourceVersion(value = SourceVersion.latestSupported())

  • init方法主要用来做一些准备工作。我们一般在这里初始化几个工具类。上述代码我们初始了与元素相关的工具类elementUtils、与日志相关的工具类messager、与文件相关的filer以及与类型相关工具类typeUtils。我们接下来会看到process主要就是通过这几个类来生成代码的。

  • process用来完成具体的程序写代码功能。在具体介绍process之前,请允许我先推荐一个库:javapoetjavapoet是由神奇的square公司开源的,它提供了非常人性化的api,来帮助开发者生成.java源文件。它的README.md文件为我们提供了丰富的例子,是我们学习的主要工具。

private Map<TypeElement, List> elementPackage = new HashMap<>();
private static final String VIEW_TYPE = "android.view.View"; private static final String VIEW_BINDER = "me.zhangkuo.apt.ViewBinding";

@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set == null || set.isEmpty()) { return false; } elementPackage.clear(); Set<? extends Element> bindViewElement = roundEnvironment.getElementsAnnotatedWith(BindView.class); //收集数据放入elementPackage中 collectData(bindViewElement); //根据elementPackage中的数据生成.java代码 generateCode(); return true; }

private void collectData(Set<? extends Element> elements){ Iterator<? extends Element> iterable = elements.iterator(); while (iterable.hasNext()) { Element element = iterable.next(); TypeMirror elementTypeMirror = element.asType(); //判断元素的类型是否是View或者是View的子类型。 TypeMirror viewTypeMirror = elementUtils.getTypeElement(VIEW_TYPE).asType(); if (typeUtils.isSubtype(elementTypeMirror, viewTypeMirror) || typeUtils.isSameType(elementTypeMirror, viewTypeMirror)) { //找到父元素,这里认为是@BindView标记字段所在的类。 TypeElement parent = (TypeElement) element.getEnclosingElement(); //根据parent不同存储的List中 List parentElements = elementPackage.get(parent); if (parentElements == null) { parentElements = new ArrayList<>(); elementPackage.put(parent, parentElements); } parentElements.add(element); }else{ throw new RuntimeException("错误处理,BindView应该标注在类型是View的字段上"); } } }

private void generateCode(){ Set<Map.Entry<TypeElement,List>> entries = elementPackage.entrySet(); Iterator<Map.Entry<TypeElement,List>> iterator = entries.iterator(); while (iterator.hasNext()){ Map.Entry<TypeElement,List> entry = iterator.next(); //类元素 TypeElement parent = entry.getKey(); //当前类元素下,注解了BindView的元素 List elements = entry.getValue(); //通过JavaPoet生成bindView的MethodSpec MethodSpec methodSpec = generateBindViewMethod(parent,elements);

String packageName = getPackage(parent).getQualifiedName().toString(); ClassName viewBinderInterface = ClassName.get(elementUtils.getTypeElement(VIEW_BINDER)); String className = parent.getQualifiedName().toString().substring( packageName.length() + 1).replace('.', '$'); ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

try { //生成 className_ViewBinding.java文件 JavaFile.builder(packageName, TypeSpec.classBuilder(bindingClassName) .addModifiers(PUBLIC) .addSuperinterface(viewBinderInterface) .addMethod(methodSpec) .build() ).build().writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } 最后看一下《Android框架体系架构(高级UI+FrameWork源码)》学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!