越来越多第三方库使用apt技术,如DBflow、Dagger2、ButterKnife、ActivityRouter、AptPreferences。在编译时根据Annotation生成了相关的代码,非常高大上但是也非常简单的技术,可以给开发带来了很大的便利。

Annotation

如果想学习APT,那么就必须先了解Annotation的基础,这里附加我另外一篇文章的地址:http://www.taoweiji.cn/2016/07/18/java-annotation

APT

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

创建Annotation Module

首先,我们需要新建一个名称为annotation的Java Library,主要放置一些项目中需要使用到的Annotation和关联代码。这里简单自定义了一个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test { }
配置build.gradle,主要是规定jdk版本
apply plugin: 'java'
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}

创建Compiler Module

创建一个名为compiler的Java Library,这个类将会写代码生成的相关代码。核心就是在这里。

配置build.gradle
apply plugin: 'java'
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
compile project(':annotation')
}
  1. 定义编译的jdk版本为1.7,这个很重要,不写会报错。
  2. AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。
  3. JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。
  4. 依赖上面创建的annotation Module。

    定义Processor类

    生成代码相关的逻辑就放在这里。

@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(Test.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
生成第一个类

我们接下来要生成下面这个HelloWorld的代码:

package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}

修改上述TestProcessor的process方法

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
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;
}

在app中使用

配置项目根目录的build.gradle
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
配置app的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
//...
dependencies {
//..
compile project(':annotation')
apt project(':compiler')
}
编译使用

在随意一个类添加@Test注解

@Test
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

点击Android Studio的ReBuild Project,可以在在app的 build/generated/source/apt目录下,即可看到生成的代码。

基于注解的View注入:DIActivity

到目前我们还没有使用注解,上面的@Test也没有实际用上,下面我们做一些更加实际的代码生成。实现基于注解的View,代替项目中的findByView。这里仅仅是学习怎么用APT,如果真的想用DI框架,推荐使用ButterKnife,功能全面。

  1. 第一步,在annotation module创建@DIActivity、@DIView注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
int value() default 0;
}
  1. 创建DIProcessor方法
@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;
}
}
  1. 使用DIActivity
@DIActivity
public class MainActivity extends Activity {
@DIView(R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DIMainActivity.bindView(this);
textView.setText("Hello World!");
}
}

实际上就是通过apt生成以下代码

public final class DIMainActivity extends MainActivity {
public static void bindView(MainActivity activity) {
activity.textView = (android.widget.TextView) activity.findViewById(R.id.text);
}
}

常用方法

常用Element子类
  1. TypeElement:类
  2. ExecutableElement:成员方法
  3. 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<? extends Element> members = processingEnv.getElementUtils().getAllMembers(typeElement);

总结

推荐阅读dagger2、dbflow、ButterKnife等基于apt的开源项目代码。JavaPoet 也有很多例子可以学习。

Example代码

https://github.com/taoweiji/DemoAPT

我们的开源项目推荐:

Android快速持久化框架:AptPreferences

AptPreferences是基于面向对象设计的快速持久化框架,目的是为了简化SharePreferences的使用,减少代码的编写。可以非常快速地保存基本类型和对象。AptPreferences是基于APT技术实现,在编译期间实现代码的生成,根据不同的用户区分持久化信息。

https://github.com/joyrun/AptPreferences

ActivityRouter路由框架:通过注解实现URL打开Activity

基于apt技术,通过注解方式来实现URL打开Activity功能,并支持在WebView和外部浏览器使用,支持多级Activity跳转,支持Bundle、Uri参数注入并转换参数类型。

https://github.com/joyrun/ActivityRouter

Android APT(编译时代码生成)最佳实践相关推荐

  1. 【字节码插桩】Android 打包流程 | Android 中的字节码操作方式 | AOP 面向切面编程 | APT 编译时技术

    文章目录 一.Android 中的 Java 源码打包流程 1.Java 源码打包流程 2.字符串常量池 二.Android 中的字节码操作方式 一.Android 中的 Java 源码打包流程 Ja ...

  2. Android系统编译时集成三方APK

    1. 前言 最近在学习Android系统开发,在开发过程中可能要到将三方apk集成到系统中的需求.目前我了解的方法有两种. 一种是在系统编译完毕之后,再将三方apk文件放置到系统目录之下.参考如何将a ...

  3. Elixir元编程-第三章 编译时代码生成技术进阶

    Elixir元编程-第三章 编译时代码生成技术进阶 注:本章内容来自 Metaprogramming Elixir 一书,写的非常好,强烈推荐.内容不是原文照翻,部分文字采取意译,主要内容都基本保留, ...

  4. Android使用APT编译时注解生成代码

    1.前言 最近在使用Butterknife的时候感觉它使用的注解挺有意思的,就了解一下,顺便自己花点时间实现一个类似的框架.加深对这块的理解,下面上干货. 2.注解 注解和class.interfac ...

  5. Android 6.0 权限管理最佳实践

    博客: Android 6.0 运行时权限管理最佳实践 github: https://github.com/yanzhenjie/AndPermission

  6. android项目编译时提示找不到*.apk(could not find the *.apk)

    eclipse编译时找不到*.apk这个问题,折磨了我很久,google很多遍,都没有适合我的方法. 搜到的结果大体有以下解决方案: 1.修改windows的locaiton 2.project属性的 ...

  7. Android 打造编译时注解解析框架

    2019独角兽企业重金招聘Python工程师标准>>> 说道注解,竟然还有各种分类,得,这记不住,我们从注解的作用来反推其分类,帮助大家记忆,然后举例强化大家的记忆,话说注解的作用: ...

  8. 编译时MSIL注入--实践Mono Cecil(1)

    紧接上两篇浅谈.NET编译时注入(C#-->IL)和浅谈VS编译自定义编译任务-MSBuild Task(csproject),在第一篇中我们简单研究了c#语法糖和PostSharp的MSIl注 ...

  9. android studio编译时提示error please select android sdk

    在做项目时协同开发时遇到的问题. 更新工程后编译提示该错误.  错误原因是,有人上传了修改的.iml文件,此处为app.iml将 <orderEntry type="jdk" ...

最新文章

  1. 根据后台的数据设置前端页面展示效果
  2. 【转】开源机器学习之RWeka
  3. c 语言怎么编译 .dll,将你的 C 语言代码编译成 .NET
  4. CF-527E(Data Center Drama) 欧拉图+构造
  5. 玩游戏老显示计算机内存不足,windows8.1玩游戏经常提示内存不足
  6. C++中char*与wchar_t*之间的转换
  7. Laravel核心解读--Facades
  8. 负载均衡、分布式、集群的关系
  9. 全军出击机器人进房间_科沃斯扫地机器人T8 POWER/MAX开箱测评推荐
  10. Javaweb图书管理系统的设计与实现(含毕业设计)
  11. 2021 年 7 款优秀免费自动回复邮件工具(优缺点比较)
  12. 基于flowplayer的视频缩略图的视频预览
  13. 低维空间到高维空间的映射
  14. 小提琴机器人拉法_小提琴的弓怎么拉 有什么技巧
  15. 从零开始学 Python 之基础篇
  16. Dubbo相关问题如何用管程实现异步转同步?
  17. 用HTML5实现十里桃花歌词的打印(一)
  18. 科研试剂半乳糖金刚烷衍生物934591-76-1,5-乙酰氨基-7,8,9-三-O-乙酰基-5-N,4-O-羰基-3,5-二脱氧-2-S-苯基-2-硫代-β-D-甘油-D-半乳-2-吡喃神经氨酸甲酯
  19. 2022年茶艺师(中级)考试题模拟考试题库及答案
  20. 解决pip3 install waring ‘The script xxx is installed in ‘/home/xxx/bin‘ which is not on PATH‘

热门文章

  1. MATLAB | 一个贼简单的粒子圣诞树
  2. java 授权码模式_Spring Security OAuth2 授权码模式的实现
  3. intouch的报警怎么用语音通知到手机用户
  4. PRML读书笔记(四)
  5. SIEMENS/西门子1214 PID/通信模板 西门子P SIEMENS/西门子1214 PID/通信模板
  6. 数位云Android SDK接入指南
  7. 计算机组成与系统结构——期末复习
  8. 分享135个ASP源码,总有一款适合您
  9. rec更新系统保留root,刷了第三方rec后怎么更新系统
  10. .NET Framework Initialization Error