Detailed explanation of compile-time annotations and implementation of ButterKnife

PS: People are a kind of creatures that are very willing to accept self-suggestion. If you give yourself negative cue, then you will easily become decadent. If you give yourself positive cue, then you will also become positive.

Today, take a look at the relevant knowledge of compile-time annotations. I believe that after manual practice, you will more easily understand frameworks such as Dagger, ARouter, ButterKnife that use compile-time annotations, and it will be easier to understand the internal source code implementation. The content is as follows:

  1. Compile-time and runtime annotations
  2. Annotation Processor APT
  3. AbstractProcessor
  4. Element和Elements
  5. Custom annotation processor
  6. Use custom annotation processor

Compile-time and runtime annotations

First understand the difference between compile time and runtime:

  1. Compile time: refers to the process by which the compiler translates the source code into code that can be recognized by the machine. In Java, it is the process of compiling the Java source code into a bytecode file recognized by the JVM.
  2. Runtime: Refers to the process of JVM allocating memory and interpreting and executing bytecode files.

Meta-annotation @Retentiondecision notes are still in operation, it can be configured at compile time strategy is as follows:

public enum RetentionPolicy {
    
    
    SOURCE,  //在编译时会被丢弃,仅仅在源码中存在
    CLASS,   //默认策略,运行时就会被丢弃,仅仅在 class 文件中
    RUNTIME  //编译时会将注解信息记录到class文件,运行时任然保留,可以通过反射获取注解信息
}

Compile-time annotations and runtime annotations eliminate the above differences, and the implementation methods are also different. Compile-time annotations are generally implemented through the annotation processor (APT), and runtime annotations are generally implemented through reflection.

For comments and reflection, you can refer to the following two articles:

What is APT

APT (Annotation Processing Tool) is a tool provided by javac that can process annotations. It is used to scan and process annotations at compile time. Simply put, you can get the information of annotations and their locations through APT, and you can use these The information is generated by the compiler in the code. Compile-time annotation is to use APT to generate code through annotation information to complete certain functions. Typical representatives include ButterKnife, Dagger, ARouter, etc.

AbstractProcessor

AbstractProcessorRealization Processoris an abstract class annotation processors to achieve annotation processors require inheritance AbstractProcessorbe extended, the main method as follows:

  • init: Initialization Processor, available from the parameter ProcessingEnvironmentacquiring the tools Elements, Types, Filerand Messagerthe like;
  • getSupportedSourceVersion: Returns the Java version used;
  • getSupportedAnnotationTypes: Returns the names of all annotations to be processed;
  • process: Get all specified annotations for processing;

The process method may be executed many times during the running process, until no other classes are generated.

Element和Elements

ElementXML is similar to the tag, in Java Elementrepresent program elements, such as classes, members, methods, each Elementexpressed shows only some structure of Elementthe operation object class, use or getKind visitor (more specific determination method ElementThe sub-categories of processing are as follows:

  • ExecutableElement
  • PackageElement
  • Parameterizable
  • QualifiedNameable
  • Type Element
  • TypeParameterElement
  • VariableElement

The above element structure corresponds to the following code structure:

// PackageElement
package manu.com.compiler;

// TypeElement
public class ElementSample {
    
    
    // VariableElement
    private int a;
    // VariableElement
    private Object other;

    // ExecutableElement
    public ElementSample() {
    
    
    }
    // 方法参数VariableElement
    public void setA(int newA) {
    
    
    }
    // TypeParameterElement表示参数化类型,用在泛型参数中
}

ElementsWe are processing Elementtools to provide only an interface, specifically the Java platform.

Custom Compilation Annotation Processor

The following use APT to achieve a comment @Bindto replace the notes to mimic the ButterKnife @BindView, Case Project is structured as follows:

APT

Api annotation module defined in @Bindthe following:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Bind {
    
    
    int value();
}

Java compiler module is a module, the auto-service Google introduced to generate the appropriate file in META-INFO, javapoet more convenient for creating Java files, the custom annotation processor BindProcessoras follows:

// 用来生成META-INF/services/javax.annotation.processing.Processor文件
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
// 用于创建Java文件
implementation 'com.squareup:javapoet:1.12.1'
/**
 * BindProcessor
 */
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
    
    
    private Elements mElements;
    private Filer mFiler;
    private Messager mMessager;

    // 存储某个类下面对应的BindModel
    private Map<TypeElement, List<BindModel>> mTypeElementMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
    
    
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        print("init");
        // 初始化Processor

        mElements = processingEnvironment.getElementUtils();
        mFiler = processingEnvironment.getFiler();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        print("getSupportedSourceVersion");
        // 返回使用的Java版本
        return SourceVersion.RELEASE_8;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        print("getSupportedAnnotationTypes");
        // 返回要处理的的所有的注解名称
        Set<String> set = new HashSet<>();
        set.add(Bind.class.getCanonicalName());
        set.add(OnClick.class.getCanonicalName());
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        print("process");
        mTypeElementMap.clear();
        // 获取指定Class类型的Element
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Bind.class);
        // 遍历将符合条件的Element存储起来
        for (Element element : elements) {
    
    
            // 获取Element对应类的全限定类名
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            print("process typeElement name:"+typeElement.getSimpleName());
            List<BindModel> modelList = mTypeElementMap.get(typeElement);
            if (modelList == null) {
    
    
                modelList = new ArrayList<>();
            }
            modelList.add(new BindModel(element));
            mTypeElementMap.put(typeElement, modelList);
        }

        print("process mTypeElementMap size:" + mTypeElementMap.size());

        // Java文件生成
        mTypeElementMap.forEach((typeElement, bindModels) -> {
    
    
            print("process bindModels size:" + bindModels.size());
            // 获取包名
            String packageName = mElements.getPackageOf(typeElement).getQualifiedName().toString();
            // 生成Java文件的文件名
            String className = typeElement.getSimpleName().toString();
            String newClassName = className + "_ViewBind";

            // MethodSpec
            MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(ClassName.bestGuess(className), "target");
            bindModels.forEach(model -> {
    
    
                constructorBuilder.addStatement("target.$L=($L)target.findViewById($L)",
                        model.getViewFieldName(), model.getViewFieldType(), model.getResId());
            });
            // typeSpec
            TypeSpec typeSpec = TypeSpec.classBuilder(newClassName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(constructorBuilder.build())
                    .build();
            // JavaFile
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .addFileComment("AUTO Create")
                    .build();

            try {
    
    
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        });

        return true;
    }

    private void print(String message) {
    
    
        if (mMessager == null) return;
        mMessager.printMessage(Diagnostic.Kind.NOTE, message);
    }
}

Which BindModelis comment @Bindsimple encapsulation information, as follows:

/**
 * BindModel
 */
public class BindModel {
    // 成员变量Element
    private VariableElement mViewFieldElement;
    // 成员变量类型
    private TypeMirror mViewFieldType;
    // View的资源Id
    private int mResId;

    public BindModel(Element element) {
        // 校验Element是否是成员变量
        if (element.getKind() != ElementKind.FIELD) {
            throw new IllegalArgumentException("element is not FIELD");
        }
        // 成员变量Element
        mViewFieldElement = (VariableElement) element;
        // 成员变量类型
        mViewFieldType = element.asType();
        // 获取注解的值
        Bind bind = mViewFieldElement.getAnnotation(Bind.class);
        mResId = bind.value();
    }

    public int getResId(){
        return mResId;
    }

    public String getViewFieldName(){
        return mViewFieldElement.getSimpleName().toString();
    }

    public TypeMirror getViewFieldType(){
        return mViewFieldType;
    }
}

Create the file to be generated in the bind module

/**
 * 初始化
 */
public class BindKnife {
    
    
    public static void bind(Activity activity) {
    
    
        // 获取activity的全限定类名
        String name = activity.getClass().getName();
        try {
    
    
            // 反射创建并注入Activity
            Class<?> clazz = Class.forName(name + "_ViewBind");
            clazz.getConstructor(activity.getClass()).newInstance(activity);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Of course, ButterKnife should cache the created objects, and will not create new objects every time. Although reflection is also used, the reflection test is much reduced compared with runtime annotations, so the performance is also better than runtime annotations. Even better, this can be regarded as the difference between compile-time annotations and runtime annotations.

Use custom annotation processor

The usage is similar to ButterKnife, as follows:

public class MainActivity extends AppCompatActivity{
    
    
   
    @Bind(R.id.tvData)
    TextView tvData;

    @Override
     public void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BindKnife.bind(this);
        tvData.setText("data");
    }
}

Understanding compile-time annotations may not immediately make wheels, but it is very helpful when looking at other frameworks, and there is one more way to solve problems.

Insert picture description here

Guess you like

Origin blog.csdn.net/jzman/article/details/111350272