Android APT实践 实现Butterknife

APT详解

现在越来越多的三方库运用了APT技术,如:Dagger2、ButterKnife、ARouter等,在编译时根据annotation生成相关的代码逻辑,动态生成java,class文件给开发带来了很大的便利

APT的含义
APT 的全称为:Annotation Processing Tool 可以解释为注解处理器,
它对源代码文件进行检测找出其中的Annotation,使用指定的Annotation进行额外的处理。
Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

实现一个注解处理器
本例实现gradle版本是3.1.2

第一步:先建立一个Android Studio 工程
第二部:建立一个名为annotationlib的java lib,注意只能是java lib,不可以是android lib,这个里面主要存储我们自定义的注解,注解库指定JDK版本为1.7

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

在这个库中自定义注解

//编译时注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
    String value();
}

第三步:建立一个名为TestCompiler 的java lib,这个库主要存储注解处理器,注意,这里必须为Java库,不然会找不到javax包下的相关资源
看下build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(':annotationlib')
    //JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。
    implementation 'com.squareup:javapoet:1.11.1'
    //AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

在这个库里,我们定义自己的注解处理器MyProcessor,每一个处理器都是继承自AbstractProcessor,必须复写 process(),一般我们复写4个方法

/**
 * 每一个注解处理器类都必须有一个空的构造函数,默认不写就行;
 */
public class MyProcessor extends AbstractProcessor {

    /**
     * init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。
     * ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
     * @param processingEnv 提供给 processor 用来访问工具框架的环境
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    /**
     * 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
     * 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素
     * @param annotations   请求处理的注解类型
     * @param roundEnv  有关当前和以前的信息环境
     * @return  如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;
     *          如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
     */
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        return false;
    }

    /**
     * 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
     * @return  注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合
     */
    @Override
    public Set getSupportedAnnotationTypes() {
        Set annotataions = new LinkedHashSet();
        annotataions.add(MyAnnotation.class.getCanonicalName());
        return annotataions;
    }

    /**
     * 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6
     * @return  使用的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

也可以注解的形式规定JDK版本和处理的注解

//指定编译的Java版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//指定处理的注解
@SupportedAnnotationTypes({"com.baidu.bpit.aibaidu.annotationlib.MyAnnotation"})
public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {


        MethodSpec main = MethodSpec.methodBuilder("main")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(String[].class, "args")
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                .build();
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)
                .build();
        JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
                .build();
        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;

    }
}

我们这个处理器生成一个HelloWorld的类

运行注解处理器
在运行前首先,要让主app依赖俩个java lib,看下主app的build.gradle

android {
    compileSdkVersion 28
    defaultConfig {
       ....
     	//记得加上这个
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath true
            }
        }
    }
 

dependencies {
    implementation project(':annotationlib')
    //用annotationProcessor project(':TestCompiler')替换    implementation project(':TestCompiler')
    //annotationProcessor 大体来讲它有两个作用:
    //能在编译时期去依赖注解处理器并进行工作,但在生成 APK 时不会包含任何遗留的东西
    //能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件
    annotationProcessor project(':TestCompiler')
 ...
}

这里有个问题需要注意,用annotationProcessor去依赖注解处理器,这样可以
1 能在编译时期去依赖注解处理器并进行工作,但在生成 APK 时不会包含任何遗留的东西
2能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件


这个时候你去编译还是没有任何输出的,你还需要做以下步骤
1、在 processors 库的 main 目录下新建 resources 资源文件夹;

2、在 resources文件夹下建立 META-INF/services 目录文件夹;

3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;

4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;

然后再主项目中加上你的注解就行了


@MyAnnotation("main")
public class MainActivity extends AppCompatActivity {
}

最后我们先Clean以下项目,在测Rebuild 项目就可以了,我们就可以看到生成的helloworld文件
在这里插入图片描述
现在注解处理器就可以正常工作了,我们还有一个需要优化的地方,上方我们自己定义了META-INF文件夹和路径,是不是很麻烦没有没有更加方便的方式,答案是,当然有,AutoService就可以帮你解决

首先依赖这个库

  //AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。
    implementation 'com.google.auto.service:auto-service:1.0-rc2'

然后在你的处理器上加上

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    // ...
}

只需要在刚才MyProcessor上方加上这句注解就行了,是不是很方便

最后我们看一下整个项目的目录
在这里插入图片描述

APT实战 实现一个简单版的Butterknife

首先在注解库里面定义俩个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DiActivity {
}

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

然后再注解处理器库里实现一个注解处理器

@AutoService(Processor.class)
public class DIProcessor extends AbstractProcessor {
    private Elements elementUtils;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 规定需要处理的注解
        return Collections.singleton(DiActivity.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("DIProcessor");
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DiActivity.class);
        for (Element element : elements) {
            // 判断是否Class
            TypeElement typeElement = (TypeElement) element;
            List<? extends Element> members = elementUtils.getAllMembers(typeElement);
            MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(TypeName.VOID)
                    .addParameter(ClassName.get(typeElement.asType()), "activity");
            for (Element item : members) {
                DiView diView = item.getAnnotation(DiView.class);
                if (diView == null) {
                    continue;
                }
                bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)", item.getSimpleName(), ClassName.get(item.asType()).toString(), diView.value()));
            }
            TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .superclass(TypeName.get(typeElement.asType()))
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(bindViewMethodSpecBuilder.build())
                    .build();
            JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    private String getPackageName(TypeElement type) {
        return elementUtils.getPackageOf(type).getQualifiedName().toString();
    }

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

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

然后再主app里面使用


@DiActivity()
public class MainActivity extends AppCompatActivity {

    @DiView(R.id.text)
    public TextView textView;
    @DiView(R.id.edit)
    public EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DIMainActivity.bindView(this);
        textView.setText("我是通过注解来的");
        editText.setText("3333333");
    }
}

其他步骤和上面一样,不在重复叙述

运行之后就会生成一个类

public final class DIMainActivity extends MainActivity {
  public static void bindView(MainActivity activity) {
    activity.textView = (android.widget.TextView) activity.findViewById(2131165316);
    activity.editText = (android.widget.EditText) activity.findViewById(2131165237);
  }
}

在这里插入图片描述
注解处理器可以正常工作

好了本片文章正式完结

GitHub:
参考:https://juejin.im/entry/57ad3fa47db2a200540c9251
https://joyrun.github.io/2016/07/19/AptHelloWorld/

发布了100 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_34760508/article/details/94618656