学习扔物线进阶视频课程笔记。注解处理器APT

元注解

元注解,就是用来注解自定义注解的注解。
有6个,@Target、@Retention、@Documented、@Inherited、@Repeatable、@Native

@Target,目标,表示自定义注解修饰的目标,用来描述注解的使用范围。常用参数有ElementType.TYPE(表示注解可以修饰类、接口、枚举、注解类),ElementType.FIELD(表示注解可以修饰字段),ElementType.METHOD(表示注解可以修饰方法)。

@Retention,保留级别,表示需要在什么级别保存该注解信息,用来表述注解保留的时间范围。可选参数有RetentionPolicy.SOURCE(只会保留在源文件,编译器会将注解丢弃),RetentionPolicy.CLASS(注解会被编译器记录在class文件中,在虚拟机运行时没有注解信息),RetentionPolicy.RUNTIME(注解信息会被保留到运行时,因此可以被反射读取)。

@Documented,文档记录,描述在使用javadoc工具为类生成帮助文档时,是否要保留其注解信息。

@Inherited,继承,使被他修饰的注解具有继承性,自定义注解被@Inherited修饰后,使用自定义注解去注解类时,子类会自动继承此注解。

@Repeatable,可重复,使被他修饰的注解可以在相同的程序元素中重复使用,java8新增。

@Native,使用@Native修饰成员变量,表示这个变量可以被本地代码引用,常被代码生成工具使用,java8新增。

自定义注解源码:

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {

}

反射实现ButterKnife

  1. 创建@BindView注解
  2. 用反射获取Field[],然后找到加了@BindView 的字段,自动调用findViewById来绑定对象

代码:

//注解类
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {

    int value();
}

//Binder类
public class Binder {

    public static void bind(Activity activity) {
        Log.d("JmBinder", "JmBinder.bind  start");
        for (Field field : activity.getClass().getDeclaredFields()) {
            BindView jmBindView = field.getAnnotation(BindView.class);
            if (jmBindView != null) {
                try {
                    field.setAccessible(true);
                    field.set(activity, activity.findViewById(jmBindView.value()));
                    Log.d("JmBinder", "JmBinder.bind  success.  field=" + field);
                } catch (Exception e) {

                }
            }
        }
    }
}


//在activity的使用
    @BindView(R.id.btn_retrofit)
    lateinit var btn_retrofit: Button

        override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        Binder.bind(this)
        
        btn_retrofit.setOnClickListener {
            startActivity(Intent(this@MainActivity, RetrofitActivity::class.java))
        }
 }

可以看到,使用简单的几行代码,就可以实现一个简化版本的ButterKnife。但是用这种方式实现有性能问题,反射遍历activity的所有字段,找到注解修饰的字段后,再反射赋值,会随着字段的增多和注解的增多,bind的耗时也会增多。

依赖注入

什么是依赖注入?把依赖的决定权交给外部,就是依赖注入。
Dagger:外部的依赖图来决定依赖的值,对象自己只负责索要,而不负责指定值,所以是依赖注入。
ButterKnife:类自己决定依赖的获取,只把执行过程交给ButterKnife,所以只是一个视图绑定,而不是依赖注入。

APT

APT:Annotation Processing Tool,注解处理器。
原理:编译过程中读取源码,然后生成新的代码和文件,再放在一起进行编译。
思路:每个使用@BindView注解修饰字段的Activity,都通过APT生成一个ActivityBinding类,然后在将findViewById的代码添加到ActivityBinding类的构造方法中,在activity中调用Binder.bind时,通过反射创建ActivityBinding类对象,就等同于调用所有的findViewById。

APT实战

在wifikit中,通过APT的方式将所有的页面id类添加到数组,生成代码,初始化时通过反射拿到所有的id类。

注解类:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
@Inherited
public @interface WifikitUi {

}

AnnotationProcessor项目结构:

Processor代码:


//@AutoService(Processor.class) //使用auto-service添加Processor
public class WifikitUiProcessor extends AbstractProcessor {

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

    private Filer filer = null;

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

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        System.out.println("WifikitUiProcessor  init  filer=" + filer);
    }

    private static final String      PACK_NAME  = "com.android.gps.tracker.wifikit.ui";
    private static final String      CLASS_NAME = "InterfaceIds2";
    private static final Set<String> ids        = new HashSet<>();
    private static       boolean     created    = false;

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        System.out.println("WifikitUiProcessor  process start");

        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(WifikitUi.class);
        for (Element element : elements) {
//            System.out.println("WifikitUiProcessor  WifikitUi element.getClass=" + element.getClass());
            Symbol symbol = (Symbol) element;
            boolean isInterface = symbol.isInterface();
            boolean isAbstract = symbol.getModifiers().contains(Modifier.ABSTRACT);
            if (!isInterface && !isAbstract) {
                String qualifiedName = symbol.getQualifiedName().toString();
                ids.add(qualifiedName);
                System.out.println("WifikitUiProcessor  WifikitUi qualifiedName=" + qualifiedName);
            }

        }

        createFile();

        return true;
    }

    private void createFile() {
        if (created) {
            return;
        }
        created = true;
        System.out.println("WifikitUiProcessor  createFile start.  ids.size=" + ids.size());
        try {
            ClassName className = ClassName.get(PACK_NAME, CLASS_NAME);

            FieldSpec idList = FieldSpec.builder(ArrayList.class, "idList")
                    .addModifiers(Modifier.PUBLIC)
                    .build();

            String initList = "    idList=new ArrayList<>();\n";
            StringBuilder addId = new StringBuilder();
            for (String id : ids) {
                addId.append("    idList.add(\"").append(id).append("\");\n");
            }
            MethodSpec constructor = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addCode(initList)
                    .addCode(addId.toString())
                    .build();

            TypeSpec builtClass = TypeSpec.classBuilder(className)
                    .addModifiers(Modifier.PUBLIC)
                    .addField(idList)
                    .addMethod(constructor)
                    .build();

            JavaFile javaFile = JavaFile.builder(PACK_NAME, builtClass).build();

            javaFile.writeTo(filer);

//            try {
//                if (filer instanceof JavacFiler) {
//                    JavacFiler javacFiler = (JavacFiler) filer;
//                    javacFiler.close();
//                }
//            } catch (Exception ee) {
//
//            }
            System.out.println("WifikitUiProcessor  createFile end");
        } catch (Exception e) {
            System.out.println("WifikitUiProcessor  createFile e=" + e);
        }
    }
}


//gradle配置
plugins {
    id 'java-library'
}

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

dependencies {
    implementation project(path:':jm-annotations')
//    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.squareup:javapoet:1.13.0'
}

在Processor中使用javapoet库来生成java代码

申明注解有两种方式,1是通过配置javax.annotation.processing.Processor路径实现,2是使用@AutoService注解实现

app项目中使用APT的gradle配置

dependencies {

    //apt处理注解
    implementation project(path:':jm-annotations')
    annotationProcessor project(path:':jm-apt')
}

app项目中的注解使用如下:


自动生成的代码如下:

package com.android.gps.tracker.wifikit.ui;

import java.util.ArrayList;

public class InterfaceIds2 {
  public ArrayList idList;

  public InterfaceIds2() {
        idList=new ArrayList<>();
        idList.add("com.android.gps.tracker.wifikit.ui.ids.ID11");
  }
}