专栏首页Android研究院组件化专题-APT实战

组件化专题-APT实战

APT 详解APT 实战常用方法常用Element子类赞赏

APT 详解

apt为何如此重要呢?现今越来越多的第三方库使用了apt技术,Dagger2、ButterKnife、ARouter等,在编译时根据annotation生成相关的代码逻辑,动态的生成Java class文件给开发带来了很大的便利。

首先要懂得annotation (注解)相关的基础知识。

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

下面我们通过一个例子来讲解apt实现的原理。本例实现gradle版本是3.0.1

  1. 新建一个名为annotationlib的java library,注意是Java library,不能是Android的library
1@Target(ElementType.TYPE) // 注解作用在类上
2@Retention(RetentionPolicy.CLASS)
3public @interface Test {
4    String path();
5}

build.gradle 配置

1apply plugin: 'java-library'
2
3dependencies {
4    implementation fileTree(dir: 'libs', include: ['*.jar'])
5}
6
7sourceCompatibility = "1.7"
8targetCompatibility = "1.7"
  1. 创建一个Java library 名为 TestCompiler

注意gradle的配置

 1apply plugin: 'java-library'
 2
 3dependencies {
 4    implementation fileTree(dir: 'libs', include: ['*.jar'])
 5    implementation project(':annotationlib')
 6    implementation 'com.google.auto.service:auto-servic:1.0-rc4'
 7    implementation 'com.squareup:javapoet:1.11.1'
 8}
 9
10sourceCompatibility = "1.7"
11targetCompatibility = "1.7"

com.google.auto.service:auto-service:1.0-rc4 是注册Processor的

com.squareup:javapoet:1.11.1 是生成class文件的第三方库

定义Processor类

 1@AutoService(Processor.class)
 2//指定编译的Java版本
 3@SupportedSourceVersion(SourceVersion.RELEASE_7)
 4//指定处理的注解
 5@SupportedAnnotationTypes({"demo.prim.com.annotationlib.Test"})
 6public class MyClass extends AbstractProcessor {
 7    @Override
 8    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
 9        // 创建方法
10        MethodSpec main = MethodSpec.methodBuilder("main")
11                .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
12                .addParameter(String[].class,"args")
13                .addStatement("$T.out.println($S)",System.class,"Hello JavaPoet")
14                .build();
15        //创建类
16        TypeSpec typeSpec = TypeSpec.classBuilder("HelloWord")
17                .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
18                .addMethod(main)
19                .build();
20
21        //创建Java文件
22        JavaFile file = JavaFile.builder("com.ecample.test", typeSpec)
23                .build();
24
25        try {
26            file.writeTo(processingEnv.getFiler());
27        } catch (IOException e) {
28            e.printStackTrace();
29        }
30        return true;
31    }
32}

上述代码会生成如下的类

1public final class HelloWord {
2  public static void main(String[] args) {
3    System.out.println("Hello JavaPoet");
4  }
5}
  1. 在app中使用 这里需要注意如果gradle版本低于3.0.0 则需要如下配置
1配置项目根目录的build.gradle
2dependencies {
3    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
4}
 1配置app的build.gradle
 2apply plugin: 'com.android.application'
 3apply plugin: 'com.neenbedankt.android-apt'
 4//...
 5dependencies {
 6    //..
 7    compile project(':annotation')
 8    apt project(':compiler')
 9}
10

编译使用

 1在随意一个类添加@Test注解
 2
 3@Test(path = "main")
 4public class MainActivity extends AppCompatActivity {
 5   @Override
 6   protected void onCreate(Bundle savedInstanceState) {
 7       super.onCreate(savedInstanceState);
 8       setContentView(R.layout.activity_main);
 9   }
10}

点击Android Studio的Rebuild Project 会生成如下文件:

image.png

这时便可以调用HelloWord类了。

APT 实战

通过apt来简化 textView = findViewById(R.id.textView);

一般的可以这样写:

1public class ManagerFindByMainActivity {
2
3    public void findById(MainActivity activity) {
4        activity.textView = activity.findViewById(R.id.textView);
5    }
6}

上面一段代码,这样写和之前的没有区别,反而每有一个Activity都要新建一个类去找到ID。那么我们是否可以通过apt来动态生成这个类呢?

答案当然是可以的。

  1. 在annotation lib 中创建两个注解
 1@Target(ElementType.TYPE)
 2@Retention(RetentionPolicy.CLASS)
 3public @interface DIActivity {
 4
 5}
 6
 7@Target(ElementType.FIELD)
 8@Retention(RetentionPolicy.RUNTIME)
 9public @interface BYView {
10    int value() default 0;
11}
  1. 在compiler lib 中创建processor

gradle 配置:

1dependencies {
2    implementation fileTree(dir: 'libs', include: ['*.jar'])
3    implementation 'com.google.auto.service:auto-service:1.0-rc4'
4    implementation 'com.squareup:javapoet:1.11.1'
5    implementation project(':lib-annotation')
6}
7
8sourceCompatibility = "1.7"
9targetCompatibility = "1.7"

核心代码实现:

 1@AutoService(Processor.class)
 2@SupportedSourceVersion(SourceVersion.RELEASE_7)
 3@SupportedAnnotationTypes({Constance.DIACTIVITY})
 4public class ByProcessor extends AbstractProcessor {
 5
 6    private Elements elementUtils;
 7
 8    private Types typeUtils;
 9
10    @Override
11    public synchronized void init(ProcessingEnvironment processingEnvironment) {
12        super.init(processingEnvironment);
13        elementUtils = processingEnvironment.getElementUtils();
14        typeUtils = processingEnvironment.getTypeUtils();
15    }
16
17    @Override
18    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
19        if (set != null) {
20            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(DIActivity.class);
21            if (elementsAnnotatedWith != null) {//获取设置DIActivity 注解的节点
22                //判断注解的节点是否为Activity
23                TypeElement typeElement = elementUtils.getTypeElement(Constance.Activity);
24                for (Element element : elementsAnnotatedWith) {
25
26                    TypeMirror typeMirror = element.asType();//获取注解节点的类的信息
27                    DIActivity annotation = element.getAnnotation(DIActivity.class);//获取注解的信息
28                    if (typeUtils.isSubtype(typeMirror, typeElement.asType())) {//注解在Activity的类上面
29
30                        TypeElement classElement = (TypeElement) element;//获取节点的具体类型
31
32                        //创建参数  Map<String,Class<? extends IRouteGroup>>> routes
33                        ParameterSpec altlas = ParameterSpec
34                                .builder(ClassName.get(typeMirror), "activity")//参数名
35                                .build();
36
37                        //创建方法
38                        MethodSpec.Builder method = MethodSpec.methodBuilder
39                                ("findById")
40//                                .addAnnotation(Override.class)
41                                .addModifiers(PUBLIC, STATIC)
42                                .returns(TypeName.VOID)
43                                .addParameter(altlas);
44
45                        //创建函数体
46
47//获取TypeElement的所有成员变量和成员方法
48                        List<? extends Element> allMembers = elementUtils.getAllMembers(classElement);//??
49
50                        //遍历成员变量 
51                        for (Element member : allMembers) {
52                        //找到被BYView注解的成员变量
53                            BYView byView = member.getAnnotation(BYView.class);
54                            if (byView == null) {
55                                continue;
56                            }
57                            //构建函数体
58                            method.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)",
59                                    member.getSimpleName(),//注解节点变量的名称
60                                    ClassName.get(member.asType()).toString(),//注解节点变量的类型
61                                    byView.value()));//注解的值
62                        }
63
64                        //创建类
65                        TypeSpec typeSpec = TypeSpec.classBuilder("ManagerFindBy" + element.getSimpleName())
66                                .addModifiers(PUBLIC, FINAL)//作用域
67                                .addMethod(method.build())//添加方法
68                                .build();
69
70                        //创建Javaclass 文件
71
72                        JavaFile javaFile = JavaFile.builder("com.prim.find.by", typeSpec).build();
73
74                        try {
75                            javaFile.writeTo(processingEnv.getFiler());
76                        } catch (IOException e) {
77                            e.printStackTrace();
78                        }
79
80                    } else {
81                        throw new IllegalArgumentException("@DIActivity must of Activity");
82                    }
83                }
84
85            }
86            return true;
87        }
88        return false;
89    }
90}
  1. Activity 中使用注解
1implementation project(':lib_annotation')
2
3annotationProcessor project(':lib_compiler')
 1@DIActivity
 2public class MainActivity extends AppCompatActivity {
 3
 4    @BYView(R.id.textView)
 5    public TextView textView;
 6
 7    @BYView(R.id.textView1)
 8    public TextView textView1;
 9
10    @BYView(R.id.textView2)
11    public TextView textView2;
12
13    @BYView(R.id.button)
14    public Button button;
15}
16
17@DIActivity
18public class SencodActivity extends AppCompatActivity {
19
20    @BYView(R.id.sencodText)
21    public TextView sencodText;
22}

然后RebuildProject,就会得到:

image.png

  1. 使用生成的类
 1@Override
 2    protected void onCreate(Bundle savedInstanceState) {
 3        super.onCreate(savedInstanceState);
 4        setContentView(R.layout.activity_main);
 5        ManagerFindByMainActivity.findById(this);
 6        textView.setText("我是APT找到的");
 7        textView1.setText("我是APT找到的 --> 1");
 8        textView2.setText("我是APT找到的 --> 2");
 9        button.setText("我被APT找到我要跳转");
10    }
11
12    public void click(View view) {
13        Intent intent = new Intent(this, SencodActivity.class);
14        startActivity(intent);
15    }

image.png

项目地址

常用方法

常用Element子类
  • TypeElement:类
  • ExecutableElement:成员方法
  • VariableElement:成员变量
  • 通过包名和类名获取TypeName TypeName targetClassName = ClassName.get(“PackageName”, “ClassName”);
  • 通过Element获取TypeName TypeName type = TypeName.get(element.asType());
  • 获取TypeElement的包名 String packageName = processingEnv.getElementUtils().getPackageOf(type).getQualifiedName().toString();
  • 获取TypeElement的所有成员变量和成员方法 List members = processingEnv.getElementUtils().getAllMembers(typeElement);

参考文章

Android的组件化专题: 组件化配置

APT实战

路由框架原理

模块间的业务通信

本文分享自微信公众号 - Android研究院(androidlinksu),作者:JakePrim

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 搞定数据结构-栈和队列

    如下,使用栈结构操作. “网”这个错别字在栈顶,“网”改成”望”只需要将“网”从栈顶移除重新写入”望”.

    用户3045442
  • Android组件化专题 - 路由框架原理

    在路由框架之前,我们先了解什么是APT,并实践ButterKnife绑定findById的小功能。为什么先要讲解apt,因为路由的实现apt是核心的代码.看下面...

    用户3045442
  • 强大灵活的WebView代理库-PrimWeb

    PrimWeb 是一个代理的WebView基于的 Android WebView 和 腾讯 x5 WebView,容易、灵活使用以及功能非常强大的库,提供了 W...

    用户3045442
  • ROC分析时一定要告诉R分析谁

    嗨!大家好,我是一棵树,这是我第一次在解螺旋发文,还是蛮激动的。下面就开始吧! 今天的主题是:ROC分析时一定要告诉R分析谁 用到的软件是:R语言 用到的R包是...

    挑圈联靠
  • 看到他我一下子就悟了-- 泛型(2)

       先说些题外话,只所以写这些东西。是看了CSDN上的曹版主的一篇:手把手教编程,不知道有没有人愿意参与。说实话,我工作四年,总感觉晕晕乎乎的,好多技术都 懂...

    hbbliyong
  • 这是EnterLib PIAB的BUG吗?

    在默认的情况下,EnterLib的PIAB采用基于TransparentProxy/RealProxy的机制实现对方法调用的拦截,进而实现了对横切关注点(Cro...

    蒋金楠
  • Nodejs学习笔记(七)--- Node.js + Express 构建网站简单示例

    前言   上一篇学习了一些构建网站会用到的一些知识点 https://cloud.tencent.com/developer/article/1020636  ...

    Porschev
  • 简单谈一谈内部类的使用场景

    作为一个类的编写者,我们很显然需要对这个类的使用访问者的访问权限做出一定的限制,我们需要将一些我们不愿意让别人看到的操作隐藏起来,

    BWH_Steven
  • 深入理解class的继承,助你成为优秀的前端

    Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

    前端老鸟
  • POI -纯java代码实现导出excel表格

    Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。 HSSF ...

    小帅丶

扫码关注云+社区

领取腾讯云代金券