Android APT注解处理器

一、编译时技术简介
APT ( Annotation Processing Tool ) 注解处理工具 ;

编译时技术 , 广泛应用在当前主流框架中 , 如 JetPack 中的 DataBinding , Room , Navigatoion , 第三方 ButterKnife , ARouter 等框架 ;

编译时技术 最重要的作用就是在编译时可以 生成模板代码 ;
由于生成代码操作是在编译时进行的 , 不会对运行时的性能产生影响 ;

程序的周期 :
源码期 : 开发时 , 刚编写完 " .java " 代码 , 还未编译之前 , 就处于源码期 ;
编译期 : 程序由 java 源码编译成 class 字节码文件 ;
运行期 : 将字节码文件加载到 Java 虚拟机中运行 ;

编译时技术 APT 作用于 编译期 , 在这个过程中使用该技术 , 生成代码 ;
编译时技术 2 22 大核心要素 : 在编译时 , 执行生成代码的逻辑 , 涉及到两个重要概念 ;
① 编译时注解 ;
② 注解处理器 ;

举例说明 : 使用 ButterKnife 时会依赖两个库 ,

dependencies {
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}

其中
com.jakewharton:butterknife:10.2.3 是 编译时的注解 ,
com.jakewharton:butterknife-compiler:10.2.3 是 注解处理器 ;

二、ButterKnife 原理分析
使用 ButterKnife :

① 添加依赖 :

dependencies {
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}

② Activity 中使用 ButterKnife :

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv1)
    TextView tv1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv1.setText("ButterKnife");
    }
}

BindView 注解分析 : 在 TextView tv1成员变量处添加了 @BindView(R.id.tv1) 注解 ;
@Target(FIELD) 元注解 : 表示其作用与类的成员字段 ;
@Retention(RUNTIME) 元注解 : 表示该注解保留到运行时阶段 ;
int value() 注解属性 : 只有一个注解属性 , 并且属性名是 value , 则使用注解时 “value =” 可省略 ;

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

@Retention(RetentionPolicy.RUNTIME) //注解的生命周期,保留时间到类文件级别
@Target(ElementType.FIELD) //作用域在字段上
public @interface BindView {
    int value();
}

TextView tv1 需要使用 findViewById 进行赋值 , 在上述代码中没有写 findViewById 相关的代码 ; 肯定是在某个地方执行了 findViewById 的方法 ;
ButterKnife.bind(this) 代码就是执行了 findViewById 方法 ;

ButterKnife 用到了编译时技术会在项目编译时 , 会生成 MainActivity_ViewBinding 类 , 在该类中 , 会查找添加了 @BindView 直接的成员变量 , 再获取 注解属性 value 的值 , 然后调用 findViewById 方法获取组件并为成员变量赋值 ;

// Generated code from Butter Knife. Do not modify!

import android.view.View;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;

    target.tv1= Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv1'", TextView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.hello = null;
  }
}

三、ButterKnife 生成 Activity_ViewBinding 代码分析
调用 ButterKnife 静态方法 Unbinder bind(@NonNull Activity target) , 传入 Activity 对象 , 在方法中调用了 ButterKnife 的 bind 方法 ;

在 bind 方法中 , 先获取了 Activity 的类对象 ,

Class<?> targetClass = target.getClass();

然后将类对象传入了 findBindingConstructorForClass 方法 ,

Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

在 findBindingConstructorForClass 方法中 , 获取了某个构造方法 ,

Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);

获取 Activity 类对象名称 , 即 " com.butterknife.demo.MainActivity " ,

String clsName = cls.getName();

得到名称后 , 判断该类对象是否是系统的 API , 如果是则退出 ; 如果不是 , 则继续向下执行 ,

if (clsName.startsWith("android.") || clsName.startsWith("java.")
    || clsName.startsWith("androidx.")) {
  if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
  return null;
}

拼装要生成的类名称 , “com.butterknife.demo.MainActivity_ViewBinding” , 并自动生成该类 ;

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

ButterKnife 涉及到的源码 :

public final class ButterKnife {
  /**
   * BindView annotated fields and methods in the specified {@link Activity}. The current content
   * view is used as the view root.
   *
   * @param target Target activity for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }

  /**
   * BindView annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link View} as the view root.
   *
   * @param target Target class for view binding.
   * @param source View root on which IDs will be looked up.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
}

了解了butterKnife原理之后,接下来字节手写butterknife注解实现。

四、创建 编译时注解 和 注解处理器

4.1 使用 Android Studio 开发 Android 项目时 , 使用到编译时技术 , 都要用到 编译时注解 和 注解处理器 ;
编译时注解 和 注解处理器 一般都创建为 Java or Kotlin Library 类型的 Module ;
右键点击工程名 , 选择 " New / Module " 选项 ,
在弹出的 " Create New Module " 对话框中 , 这里选择 Module 的类型为 Java or Kotlin Library ;
设置编译时注解 依赖库名称 annotation , 注意语言选择 Java ; 暂时不涉及 Kotlin 注解 ;

使用上述相同的方式 , 创建 annotation-compiler 注解处理器 依赖库 , 这两个 Module 的类型都是 " Java or Kotlin Library " ;

4.2 接下来为项目app模块添加annotation和annotation-compiler模块的依赖
在主应用 " app " 中 , 依赖上述 annotation 编译时注解 依赖库 和 annotation-compiler 注解处理器 依赖库 ;
右键点击应用 , 选择 " Open Modules Settings " 选项 ,
在 " Project Structure " 对话框中选择 " Dependencies " 选项卡 , 选择主应用 " app " , 点击 " + " 按钮 , 选择添加 " Module Dependency " 依赖库 ,
将 annotation 编译时注解 依赖库 和 annotation-compiler 注解处理器 依赖库 添加到主应用 " app " 的依赖中 ;
添加依赖完成 ;

点击 " OK " 按钮后 , 在 build.gradle 构建脚本中自动生成的依赖 :

dependencies {
    implementation project(path: ':annotation-compiler')
    implementation project(path: ':annotation')
}

注意 : 对于 annotation-compiler 注解处理器 依赖库 不能使用 implementation , 必须使用 annotationProcessor ,

dependencies {
    annotationProcessor project(path: ':annotation-compiler')
    implementation project(path: ':annotation')
}

五、开发 annotation 编译时注解 依赖库 ;

创建BindView.java注解类

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

@Retention(RetentionPolicy.CLASS) //注解的生命周期,保留时间到类文件级别
@Target(ElementType.FIELD) //作用域在字段上
public @interface BindView {
    int value();
}

六、开发 annotation-compiler 注解处理器 依赖库 ;

6.1 创建 AnnotationCompiler 注解处理器 , 该类主要作用是生成代码 , 注解处理器类必须继承 javax.annotation.processing.AbstractProcessor 类 , 这是 Java 的 API , 再 Android 中无法获取该 API , 因此 编译时注解 和 注解处理器 都必须是 Java 依赖库 ;

创建AnnotationCompiler.java

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;

/**
 * 生成代码的注解处理器
 */
public class AnnotationCompiler extends AbstractProcessor {

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

6.2 使用注解 @AutoService(Processor.class) 标注 注解处理器
上述实现的 AbstractProcessor 中的 process 方法 , 专门用于搜索 Android 源码中使用的 编译时注解 的方法 ;

程序构建时 , 发现了 annotationProcessor project(path: ':annotation-compiler') 依赖 , 使用 annotationProcessor 进行依赖 , 说明这是一个注解处理器 , 此时会到 annotation-compiler 模块中查找 注解处理器 , 注解处理器 的查找方式也是 通过 注解 标记 进行查找 , 该注解是 Google 服务库提供的 ;

如果要使用 注解 标记注解处理器 , 首先要依赖 Google 服务库 ,
在 annotation-compiler模块的build.gradle中添加

dependencies {
    //使用google的auto-service依赖库 
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
}

然后到 public class Compiler extends AbstractProcessor 注解处理器类上使用 @AutoService(Processor.class) 标注 , Android 编译器通过查找该注解 , 确定哪个类是注解处理器 ;

import java.util.Set;

import com.google.auto.service.AutoService;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;

/**
 * 生成代码的注解处理器
 */
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {

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

6.3 注解处理器 init 初始化方法
AbstractProcessor 注解处理器中 , 专门提供了一个 init 方法 , 该方法专门用于注解处理器相关初始化操作 ;
init 方法的 ProcessingEnvironment processingEnv 参数很重要 , 通过该参数可以获取 注解处理器中的各种重要工具 ;
ProcessingEnvironment 中定义了获取相关工具的接口 ;

public interface ProcessingEnvironment {
    Map<String, String> getOptions();

    Messager getMessager();

    Filer getFiler();

    Elements getElementUtils();

    Types getTypeUtils();

    SourceVersion getSourceVersion();

    Locale getLocale();
}

6.4 注解处理器 Filer 代码生成工具具
(1)通过注解生成 Java 代码需要使用 Filer 对象 , 将该对象定义到 注解处理器 成员变量中 , 在 init 方法中进行初始化操作 ;
通过 ProcessingEnvironment 可以通过调用 getFiler 方法 , 获取 Filer 对象 ;

(2)注解处理器中不能打断点进行调试 , 也不能使用 Log , System.out 打印日志 , 在注解处理器中只能通过 Messager 打印日志 ;
通过调用 ProcessingEnvironment 的 getMessager 方法 , 可以获取到 Messager 对象 ;

    //生成文件的对象
    private Filer filer;
    //日志打印
    private Messager messager;

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

七、注解处理器annotation-compiler 依赖 编译时注解annotation模块
注解处理器 需要处理 编译时注解 , 因此必须能够拿到 编译时注解 的引用 , 注解处理器 Module 需要依赖 编译时注解 Module ;

在 注解处理器 Module 的 build.gradle 的 dependencies 依赖中添加 implementation project(path: ':annotation') 依赖 ;

plugins {
    id 'java-library'
}
dependencies {
    //需要依赖annotation模块,获取这个模块的注解
    implementation project(path: ':annotation')
    //使用google的auto-service依赖库 
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

八、设置 注解处理器 支持的注解类型
注解处理器 抽象类 AbstractProcessor 中的 getSupportedAnnotationTypes 方法 , 用于声明 注解处理器 要处理的注解类型 ;
该方法的返回值是 Set<String> , 因此可以设置多个处理的 注解类型 ;
在 getSupportedAnnotationTypes 方法中构造一个 Set<String> 集合 , 向其中放置要解析注解的全类名字符串 ;

/**
     * 声明要处理的注解有哪些
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

设置 注解处理器 支持的注解类型 , 也可以使用 注解 的方式进行声明 ;
使用 @SupportedAnnotationTypes 注解 , 也可以声明 注解处理器 支持的注解类型 ;

@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface SupportedAnnotationTypes {
    /**
     * Returns the names of the supported annotation types.
     * @return the names of the supported annotation types
     */
    String [] value();
}

注意 : 两种方式二选一 , 不能同时存在 ;

九、设置 注解处理器 支持的 Java 版本
注解处理器 抽象类 AbstractProcessor 中的 getSupportedSourceVersion 方法 , 用于声明 该注解处理器 支持的 Java 版本 ;
一般情况下要支持到最新的 Java 版本 ,
通过调用 ProcessingEnvironment 类的 getSourceVersion 方法 , 可以获取最新的 Java 版本 ;

/**
     * 声明支持的java版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

设置 注解处理器 支持的 Java 语言版本 , 也可以使用 注解 的方式进行声明 ;
使用 @SupportedSourceVersion 注解 , 也可以声明 注解处理器 支持的 Java 语言版本 ;

@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface SupportedSourceVersion {
    /**
     * Returns the latest supported source version.
     * @return the latest supported source version
     */
    SourceVersion value();
}

注意 : 两种方式二选一 , 不能同时存在 ;

十、获取被 注解 标注的节点
处理注解的核心逻辑在 AbstractProcessor 中的 process 方法中实现 ;

先获取被注解标注的节点 , 搜索 BindView , 调用 process 方法的 RoundEnvironment roundEnvironment 参数的 getElementsAnnotatedWith 方法 , 即可搜索到整个 Module 中所有使用了 BindView 注解的元素 ;

Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);

将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素 , 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;

在 app 模块中 , 只有 MainActivity 中的一个 属性字段 使用 BindView 注解 , 调用 roundEnv.getElementsAnnotatedWith(BindView.class) 方法获取的元素就是 MainActivity 中的所有标记@BindView的成员变量 ;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;

import com.aapl.annotation.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv1)
    TextView tv1;
    @BindView(R.id.tv2)
    TextView tv2;
    @BindView(R.id.tv3)
    TextView tv3;
    @BindView(R.id.iv1)
    ImageView iv1;
    @BindView(R.id.iv2)
    ImageView iv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv1.setText("bind view success");
    }
}

假设在 若干 Activity 中 , 若干位置 , 使用了 BindView 注解 , 那么在获取的所有使用了 BindView 注解的字段 Set<? extends Element> elements 中装载了所有的使用了该注解的字段 , 这些字段来自不同的 Activity 中 ;

这就需要将 Set<? extends Element> elements 中的 字段 按照 Activity 上下文进行分组 , 以便生成代码 ;

这样每个 Activity 界面都对应若干个 Set<? extends Element> elements 中的元素 ;

十一、Element 注解节点类型
使用注解标注的 Element 节点类型 :

ExecutableElement : 使用注解的 方法 节点类型 ;

VariableElement : 使用注解的 字段 节点类型 , 类的成员变量 ;

TypeElement : 使用注解的 类 节点类型 ;

PackageElement : 使用注解的 包 节点类型 ;

上述 4个类都是 javax.lang.model.element.Element 的子类 ;

app module下完整源码参考如下:

ButterKnife.java代码:

import android.app.Activity;
import android.os.IBinder;

import java.lang.reflect.Constructor;

public class ButterKnife {
    /**
     * 获取到生成的XXXActivity_ViewBinder类文件
     * @param obj
     */
    public static void bind(Object obj) {
        Class<?> clazz = obj.getClass();
        String clazzName = clazz.getName()+"_ViewBinder";
        try {
            //获取到XXXActivity_ViewBinder的class,并创建class实例对象
            Class<?> binderClazz = Class.forName(clazzName);
            //确定一个类binderClass是不是继承(或实现自)IButterKnife
            if (IButterKnife.class.isAssignableFrom(binderClazz)) {
                IButterKnife butterKnife = (IButterKnife) binderClazz.newInstance();
                butterKnife.bind(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

IButterKnife.java代码:

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

AnnotationCompiler.java代码:

import com.aapl.annotation.BindView;
import com.aapl.annotation.OnClick;
import com.google.auto.service.AutoService;

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.Map;
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;

/**
 * 注解处理器(目的是生成一个java文件,生成一个窗体的findViewById和onClick的代码)
 * 1.继承AbstractProcessor
 * 2.注册服务,注册注解处理器
 */
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {
    //生成文件的对象
    private Filer filer;
    //日志打印
    private Messager messager;

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

    /**
     * 声明要处理的注解有哪些
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    /**
     * 声明支持的java版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // PackageElement 包节点
        // TypeElement 类节点
        // ExecutableElement 方法节点
        // VariableElement 成员变量节点
        /**
         * 搜索 BindView , 将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素
         * 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;
         * 通过 getElementsAnnotatedWith 方法可以搜索到整个 Module 中所有使用了 BindView 注解的元素
         */
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        /**
         * 定义一个map集合,用来存储所有activity中所有的变量注解集合
         * key->activityName activity名字
         * value->List<VariableElement> 成员变量注解集合
         */
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elements) {
            //转化为成员变量节点
            VariableElement variableElement = (VariableElement) element;
            /**
             * 先获取该注解节点的上一级节点 , 注解节点是 VariableElement 成员字段节点,
             * 上一级节点是就是 Activity 类节点对应的 类节点 TypeElement
             */
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            String qualifiedName = typeElement.getQualifiedName().toString();
            //获取到activityName = simpleName
            String simpleName = typeElement.getSimpleName().toString();
            System.out.println("qualifiedName="+qualifiedName+", simpleName="+simpleName);
            messager.printMessage(Diagnostic.Kind.NOTE, "process==>qualifiedName="+qualifiedName+", simpleName="+simpleName);
            //把当前activity中获取到的所有变量注解的节点都加入到list集合中
            List<VariableElement> variableElements = map.get(simpleName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(simpleName, variableElements);
            }
            variableElements.add(variableElement);
        }

        if (!map.isEmpty()) {
            Writer writer = null;
            Iterator iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, List<VariableElement>> entry = (Map.Entry<String, List<VariableElement>>) iterator.next();
                String activityName = entry.getKey();
                List<VariableElement> variableElements = entry.getValue();
                //获取包名
                String packageName = "";
                TypeElement typeElement = null;
                if (variableElements != null && !variableElements.isEmpty()) {
                    packageName = getPackageName(variableElements.get(0));
                    typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                }
                // 生成一个java文件
                String bindActivityName = activityName+"_ViewBinder";
                try {
                    JavaFileObject javaFileObject = filer.createSourceFile(
                            packageName+"."+bindActivityName, typeElement);
                    writer = javaFileObject.openWriter();
                    StringBuffer sb = new StringBuffer();
                    sb.append("package "+packageName+";\n\n");
                    sb.append("import android.view.View;\n\n");
                    sb.append("public class "+bindActivityName+" implements IButterKnife<"+packageName+"."+activityName+"> {\n\n");
                    sb.append("    public void bind("+packageName+"." + activityName + " target) {\n");
                    for (VariableElement variableElement : variableElements) {
                        //获取到成员变量的名字
                        String filedName = variableElement.getSimpleName().toString();
                        //获取到注解中的值,即(R.id.xxx)
                        int resId = variableElement.getAnnotation(BindView.class).value();
                        sb.append("        target."+filedName+" = target.findViewById("+resId+");\n");
                    }
                    sb.append("");
                    sb.append("    }\n");
                    sb.append("}\n");

                    writer.write(sb.toString());

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * 获取包名
     * @param typeElement
     * @return
     */
    public String getPackageName(Element typeElement)  {
        PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
        String qualifiedName = packageElement.getQualifiedName().toString();
        return qualifiedName;
    }
}

MainActivity.java代码:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;

import com.aapl.annotation.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv1)
    TextView tv1;
    @BindView(R.id.tv2)
    TextView tv2;
    @BindView(R.id.tv3)
    TextView tv3;
    @BindView(R.id.iv1)
    ImageView iv1;
    @BindView(R.id.iv2)
    ImageView iv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv1.setText("bind view success");
    }
}

运行之后生成的MainActivity_ViewBinder.java

import android.view.View;

public class MainActivity_ViewBinder implements IButterKnife<com.aapl.butterknifedemo.MainActivity> {

    public void bind(com.aapl.butterknifedemo.MainActivity target) {
        target.tv1 = target.findViewById(2131231129);
        target.tv2 = target.findViewById(2131231130);
        target.tv3 = target.findViewById(2131231131);
        target.iv1 = target.findViewById(2131230915);
        target.iv2 = target.findViewById(2131230916);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容