apt介绍

作为Android程序员应该绝大部分分人都用过ButterKnife,Retrofit等框架,这些框架只需要在用的时候使用注解,就可以直接使用了,非常方便。并且这些框架并没有减少性能。

那么这些框架做了哪些东西呢?

我们以ButterKnife为例:

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

private Unbinder bind;

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

    textview.setText("dfjslafjsalfls");
}

@Override
protected void onDestroy() {
    bind.unbind();
    super.onDestroy();
}
复制代码

上面是我们平常使用的时候所使用的代码,这些代码到底做了什么呢?

  1. 我们给元素写上注解
  2. 在编译器执行的环节,系统收集我们所写注解的元素,并将这些元素组合成Java代码MainActivity_ViewBinding
  3. 当我们调用ButterKnife.bind的时候,通过动态注入的方式,将MainActivityMainActivity_ViewBinding关联起来,并给所有的注解所在的元素赋值。

而这些东西的核心就是在编译期间生成我们所需要的Java文件,这样我们在使用的时候就基本没有性能的影响。

网上找到的APT的流程图:



手写ButterKnife

通过上面的图我们先生成这些module



app - 主项目 butterknife - android lib annotaion - java lib compiler - java lib

在这些module的配置文件中配置
  1. project的build.gradle
// 如果你的gradle版本是3.0以上则不需要配置
buildscript {
    
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'

        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}
复制代码
  1. butterknife-compiler的build.gradle
    添加auto-service和javapoet这两个lib是用来方便我们生成Java代码的。
    auto-service: https://github.com/google/auto javapoet:
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.google.auto.service:auto-service:1.0-rc3'
    compile 'com.squareup:javapoet:1.8.0'
    implementation project(':butterknife-annotations')
}
复制代码
  1. app的build.gradle
// gradle版本小于3.0
apply plugin: 'com.neenbedankt.android-apt'

compile project(':butterknife')
compile project(':butterknife-annotations')
apt project(':butterknife-compiler')

// gradle版本大于3.0
compile project(':butterknife')
compile project(':butterknife-annotations')
annotationProcessor project(':butterknife-compiler')
复制代码
在annotation中写上注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
复制代码
在compiler中编写注解生成器

java文档: http://www.yq1012.com/api/index.html-overview-summary.html javax.annotation.processing包 javax.lang.model.element包

  1. 创建类processor继承AbstractProcessor
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
    private Elements elementUtils;
    private Filer filer;

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

    // 指定SourceVersion
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }        
}
复制代码
  1. 指定我们所需要的annotation
// 指定processortype
public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    Set<Class<? extends Annotation>> supportedAnnotations = getSupportedAnnotations();
    for (Class<? extends Annotation> supportedAnnotation : supportedAnnotations) {
        types.add(supportedAnnotation.getCanonicalName());
    }
    return types;
}

private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
    annotations.add(BindView.class);
    return annotations;
}
复制代码
  1. 在process中处理annotation
    首先将所有获取到的BindView细分到每个Activity中
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);

// 将获取到的bindview细分到每个class
Map<Element, List<Element>> elementMap = new LinkedHashMap<>();

for (Element element : elements) {
    // 返回activity
    Element enclosingElement = element.getEnclosingElement();

    List<Element> bindViewElements = elementMap.get(enclosingElement);
    if (bindViewElements == null) {
        bindViewElements = new ArrayList<>();
        elementMap.put(enclosingElement, bindViewElements);
    }
    bindViewElements.add(element);
}
复制代码

通过遍历的方式处理每个细分的Activity

// 生成代码
for (Map.Entry<Element, List<Element>> entrySet : elementMap.entrySet()) {
    Element enclosingElement = entrySet.getKey();
    List<Element> bindViewElements = entrySet.getValue();

    // public final class xxxActivity_ViewBinding implements Unbinder
    // 获取activity的类名
    String activityClassNameStr = enclosingElement.getSimpleName().toString();
    System.out.println("------------->" + activityClassNameStr);
    ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
    ClassName unBinderClassName = ClassName.get("com.fastaoe.butterknife", "Unbinder");
    TypeSpec.Builder classBuilder =
            TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
                    .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
                    .addSuperinterface(unBinderClassName)
                    // 添加属性 private MainActivity target;
                    .addField(activityClassName, "target", Modifier.PRIVATE);

    // unbind()
    ClassName callSuperClassName = ClassName.get("android.support.annotation", "CallSuper");
    MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
            .addAnnotation(Override.class)
            .addAnnotation(callSuperClassName)
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL);

    // 构造函数
    MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
            .addParameter(activityClassName, "target")
            .addModifiers(Modifier.PUBLIC)
            // this.target = target
            .addStatement("this.target = target");

    for (Element bindViewElement : bindViewElements) {
        // textview
        String fieldName = bindViewElement.getSimpleName().toString();
        // Utils
        ClassName utilsClassName = ClassName.get("com.fastaoe.butterknife", "Utils");
        // R.id.textview
        int resourceId = bindViewElement.getAnnotation(BindView.class).value();
        // target.textview = Utils.findViewById(target, R.id.textview)
        constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)", fieldName, utilsClassName, resourceId);
        // target.textview = null
        unbindMethodBuilder.addStatement("target.$L = null", fieldName);
    }


    classBuilder.addMethod(unbindMethodBuilder.build())
            .addMethod(constructorMethodBuilder.build());

    // 获取包名
    String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();

    try {
        JavaFile.builder(packageName, classBuilder.build())
                .addFileComment("自己写的ButterKnife生成的代码,不要修改!!!")
                .build().writeTo(filer);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
复制代码
在butterknife中编写注入代码
public class ButterKnife {

    public static Unbinder bind(Activity activity) {
        try {
            Class<? extends Unbinder> bindClazz = (Class<? extends Unbinder>)
                    Class.forName(activity.getClass().getName() + "_ViewBinding");
            // 构造函数
            Constructor<? extends Unbinder> bindConstructor = bindClazz.getDeclaredConstructor(activity.getClass());

            Unbinder unbinder = bindConstructor.newInstance(activity);
            return unbinder;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return Unbinder.EMPTY;
    }
}
复制代码
使用

我们的使用方式就和正真的ButterKnife一样了:

public class MainActivity extends AppCompatActivity {

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

    private Unbinder bind;

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

        textview.setText("修改后的文字");
    }

    @Override
    protected void onDestroy() {
        bind.unbind();
        super.onDestroy();
    }
}
复制代码