Android进阶:注解+APT方式,仿ButterKnife实现

前言

java学习时都应该学习过注解,但是一直不知道注解怎么用,怎么发挥他的效果。最近开始Android学习,然后开始看Butterknife源码学习。如果你也想学习butterknife的实现,那么可以看看这篇文章。

注解在我们android开发和java开发中有很多作用,今天我们就来介绍下他的一种高级用法:注解处理器(APT:Annotation Processing Tool)

注解基础知识

1、元注解:

1、@Target(xxx):目标,代表该注解可用在何处
参数类型有:ElementType.FIELD //注解用在属性上
ElementType.TYPE//注解用在类上
ElementType.CONSTRUCTOR //注解用在构造方法上
ElementType.METHOD //注解用在方法上
ElementType.ANNOTATION_TYPE //注解用在注解类型上
ElementType.LOCAL_VARIABLE //用在本地变量上
ElementType.PARAMETER //可以给一个方法内的参数进行注解
.......
还有很多,这里就不赘述了,常用的就那么几个:ElementType.FIELD、ElementType.METHOD、ElementType.TYPE

2、@Retention(RetentionPolicy.xx) :保持、保留,代表注解的作用域。
参数有:
RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME

RetentionPolicy.SOURCE:代表注解的作用只在源码层,编译后的自己码中注解会消失。

RetentionPolicy.CLASS:注解会在源码编译后的自己码中也存在,但是不会保持到VM的运行时。

RetentionPolicy.RUNTIME:注解会保持到VM运行时。

3、@Repeatable:定义注解可重复

4、@Inherit:定义子类继承父类的注解

5、@Document:将注解包含到javaDoc中

2、元注解的使用:

1、创建一个注解

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

2、使用注解

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_test)
    TextView tvTest;

    @BindView(R.id.btn_modify)
    Button btnModify;

  ........此处省略其他代码...........
}

3、运行时注解:

主要是使用元注解标识:@Retention(RetentionPolicy.RUNTIME)

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

4、编译时注解(这是我们要讲的重点,ButterKnife神器的实现原理主要也是它)

知识储备:
1、APT是一种注解解析工具:在编译期找出源代码中所有的注解信息,如果指定了注解器(继承AbstractProcessor),那么在编译期会调用这个注解器里面的代码,我们可以在这里面做一些处理, 如根据注解信息动态生成一些代码,并将代码注入到源码中。

2、使用到的工具:
工具类1 Element:表示程序的一个元素,它只在编译期存在。可以是package,class,interface,method,成员变量,函数参数,泛型类型等。
Element的子类:

  • ExecutableElement:类或者接口中的方法,构造器或者初始化器等元素
  • PackageElement:代表一个包元素程序
  • VariableElement:代表一个类或者接口中的属性或者常量的枚举类型,方法或者构造器的参数,局部变量,资源变量或者异常参数
  • TypeElement:代表一个类或者接口元素
  • TypeParameterElement:代表接口,类或者方法的泛型参数元素

通过Element可以获取什么信息呢?

1.asType() 返回TypeMirror:
    TypeMirror是元素的类型信息,包括包名,类(或方法,或参数)名/类型
    TypeMirror的子类:ArrayType, DeclaredType, DisjunctiveType, ErrorType, ExecutableType, NoType, NullType, PrimitiveType, ReferenceType, TypeVariable, WildcardType
  
2.equals(Object obj) 比较两个Element利用equals方法。
3.getAnnotation(Class annotationType) 传入注解可以获取该元素上的所有注解。
4.getAnnotationMirrors() 获该元素上的注解类型。
5.getEnclosedElements() 获取该元素上的直接子元素,类似一个类中有VariableElement。
6.getEnclosingElement() 获取该元素的父元素,
    如果是属性VariableElement,则其父元素为TypeElement,
    如果是PackageElement则返回null,
    如果是TypeElement则返回PackageElement,
    如果是TypeParameterElement则返回泛型Element
7.getKind() 返回值为ElementKind,通过ElementKind可以知道是那种element,具体就是Element的那些子类。
8.getModifiers() 获取修饰该元素的访问修饰符,public,private
9.getSimpleName() 获取元素名,不带包名,
    如果是变量,获取的就是变量名,
    如果是定义了int age,获取到的name就是age。

工具类2 ProcessingEnvironment
APT运行环境:里面提供了写新文件, 报告错误或者查找其他工具.

1.getFiler():返回用于创建新的源,类或辅助文件的文件管理器。
2.getElementUtils():返回对元素进行操作的一些实用方法的实现.
3.getMessager():返回用于报告错误,警告和其他通知的信使。
4.getOptions():返回传递给注解处理工具的处理器特定选项。
5.getTypeUtils():返回一些用于对类型进行操作的实用方法的实现。

工具类3 ElementKind:
如何判断Element的类型呢,需要用到ElementKind,ElementKind为元素的类型,元素的类型判断不需要用instanceof去判断,而应该通过getKind()去判断对应的类型

element.getKind()==ElementKind.CLASS;

工具类4 TypeKind:

TypeKind为类型的属性,类型的属性判断不需要用instanceof去判断,而应该通过getKind()去判断对应的属性

element.asType().getKind() == TypeKind.INT

基础知识了解后,下面我们开始使用自定义使用APT,通过模仿ButterKnife来进行练习
步骤1:创建一个java lib模块:annotations 注意,这个依赖model是java library

image.png

步骤2:再创建一个java library(annotation_compiler)


image.png

创建的这个java library,主要用于编译时,生成想要的代码。
这个类主要注意几点:
1、使用到了google的auto-service工具,需要在该模块build.gradle中添加。
2、模块工具类AnnotationCompiler(名字自己随意),需要使用注解@AutoService(Process.class)标识,这样才会想spring中被扫描到,然后需要extends AbstractProcessor(实现AbstractProcessor的抽象法)。
3、实现AbstractProcessor的抽象法:

//1、支持的版本:固定写支持最新版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
//2、能用来处理哪些注解
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }
//3.定义一个用来生成APT目录下面的文件的对象
    Filer filer;

    //4.初始化filer
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }
//5、编译是在这里干坏事,把想要生成的代码在此方法中实现
//这里需要用来创建Java类文件,可以使用比较好用的第三方库:JavaPoet
 @Override
    public boolean process(Set set, RoundEnvironment roundEnv) {

}

那么AbstractProcessor实现类的方法什么时候执行呢?这个时候需要运用到SPI技术。注意到BindViewProcessor类上面有一个注解@AutoService(Processor.class),这个是google的第三方库,需要在gradle里面引用。

模块build.gradle源码:

plugins {
    id 'java-library'
}

dependencies {
    implementation fileTree(dir: 'libs', includes: ['*.jar'])
    implementation project(path: ':annotations')

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
}

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

完整的AnntotationCompiler源码:

package com.xmaroon.annotation_compiler;

import com.google.auto.service.AutoService;
import com.xmaroon.annotations.BindView;

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.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * @author xuqingsong
 * 注解处理程序,用来生成代码的
 */
@AutoService(Process.class)
public class AnnotationCompiler extends AbstractProcessor {
    //1、支持的版本:固定写支持最新版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //2、能用来处理哪些注解
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    //3.定义一个用来生成APT目录下面的文件的对象
    Filer filer;

    //4.初始化filer
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    //5、编译是在这里干坏事,把想要生成的代码在此方法中实现
    @Override
    public boolean process(Set set, RoundEnvironment roundEnv) {
        if (set.isEmpty()) {
            return false;
        }
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "xuqingsong---" + set);
        Map> map = new HashMap<>();
        //获取APP中所有用到了BindView注解的对象
        //TypeElement 类
        //ExecutableElement 方法
        //VariableElement 属性
        Set elementsAnnotationWith = roundEnv.getElementsAnnotatedWith(BindView.class);
        for (Element element : elementsAnnotationWith) {
            VariableElement variableElement = (VariableElement) element;
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            Class aClass = variableElement.getEnclosingElement().getClass();
            List variableElementList = map.get(activityName);
            if (variableElementList == null) {
                variableElementList = new ArrayList<>();
                map.put(activityName, variableElementList);
            }
            variableElementList.add(variableElement);
        }

        //开始生成文件
        if (map.size() > 0) {
            Writer writer = null;
            Iterator iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                List variableElements = map.get(activityName);
                //得到包名
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    writer = sourceFile.openWriter();
                    //        package com.example.dn_butterknife;
                    writer.write("package " + packageName + ";\n");
                    //        import com.example.dn_butterknife.IBinder;
                    writer.write("import " + packageName + ".IBinder;\n");
                    //        public class MainActivity_ViewBinding implements IBinder<
                    //        com.example.dn_butterknife.MainActivity>{
                    writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
                            packageName + "." + activityName + ">{\n");
                    //            public void bind(com.example.dn_butterknife.MainActivity target) {
                    writer.write(" @Override\n" +
                            " public void bind(" + packageName + "." + activityName + " target){\n");
                    //target.tvText=(android.widget.TextView)target.findViewById(2131165325);
                    for (VariableElement variableElement : variableElements) {
                        //得到名字
                        String variableName = variableElement.getSimpleName().toString();
                        //得到ID
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //得到类型
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
                    }

                    writer.write("\n}}");

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

            }
        }
        return false;
    }
}

步骤3:在主项目app中,创建IBinder类和关联bind工具类。我们的主要目的是为了编译时生成view被绑定的代码,如图是整个项目最终生成的代码:


image.png

生成的xxxActivity_ViewBinding源码:

package com.xmaroon.myannotation;
import com.xmaroon.myannotation.IBinder;
public class MainActivity_ViewBinding implements IBinder{
 @Override
 public void bind(com.xmaroon.myannotation.MainActivity target){
target.tvTest=(android.widget.TextView)target.findViewById(2131231176);
target.btnModify=(android.widget.Button)target.findViewById(2131230823);

}}

主项目app的build.gradle源码中注意在依赖时添加上依赖库:

    implementation project(path:':annotations')
    annotationProcessor project(path:':annotation_compiler')

这里我们再捋捋:
1、创建注解类BindView,只能拿到view的id,通过BindView注解的value得到。
2、使用apt工具生成自定义代码,这段代码实现了findViewById,并且这段代码的需要传入的参数是对应的view的那个Activity。
3、为了实现bind,①定义接口IBinder类,②通过反射使用自定义生成的java代码xxxActivity_ViewBinding来调用bind。

IBinder源码:

public interface IBinder {
    /**
     * bind view
     * @param activity
     */
    void bind(T activity);
}

XButterKnife源码:

public class XButterKnife {
    public static void bind(Activity activity) {
        String name = activity.getClass().getName() + "_ViewBinding";
        try {
            Class aClass = Class.forName(name);
            IBinder iBinder = (IBinder) aClass.newInstance();
            iBinder.bind(activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

在具体的Activity中调用:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_test)
    TextView tvTest;

    @BindView(R.id.btn_modify)
    Button btnModify;

    int count = 0;

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

        btnModify.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StringBuilder value = new StringBuilder("修改了");
                if (count != 0) {
                    value.append(count);
                }
                tvTest.setText(value);
                count++;
            }
        });
    }
}

这里主要是基础的使用apt实现生成代码。编译是发现会生产对应的代码,即步骤3中的xxxActivity_ViewBinding。

注意:我在编译是总是不能生成这个代码,因为我忽略了一个小细节,使用apt时,需要一个文件将AnnotationCompiler这个自定义类运行起来,但是这个类自己操作时却是需要自己创建的

如下图:


image.png

在annotation_compiler模块下,main下的java同级目录resources文件夹中,创建META-INF => services => javax.annotation.processing.Processor

javax.annotation.processing.Processor文件中的源码:

com.xmaroon.annotation_compiler.AnnotationCompiler

创建的自定义编译生成文件类,这个文件的作用应该是相当于注册,通过注册后,才能被扫描运行起来!!!

image.png

基本功能已实现,接下来继续升级!!关注我后续文章

你可能感兴趣的:(Android进阶:注解+APT方式,仿ButterKnife实现)