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

Annotation

如果想学习APT,那么就必须先了解Annotation的基础,这里附加我另外一篇文章的地址:

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(R.id.text)",item.getSimpleName(),ClassName.get(item.asType()).toString()));
}
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. 编译时MSIL注入--实践Mono Cecil(1)

    原文:编译时MSIL注入--实践Mono Cecil(1) 紧接上两篇浅谈.NET编译时注入(C#-->IL)和浅谈VS编译自定义编译任务—MSBuild Task(csproject),在第一 ...

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

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

  3. Android 打造编译时注解解析框架 这只是一个开始

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43452969 ,本文出自:[张鸿洋的博客] 1.概述 记得很久以前,写过几篇博客 ...

  4. 在cocos2d-x-3.0 android 平台编译时提示CocosGUI.h: No such file or directory

    分类是个让人蛋疼的事情,所幸自己的博客自己做主.这是个高兴的开始. 每天抽空玩2048,终于忍受不住,于是决定自己从网上download下源码,自己编译一个出来.所有的事情都很容易,除了操蛋的中文注释 ...

  5. Android系统编译时遇到的几个.mk的疑惑。

    在Android4.2的源代码Build/prduct_config.mk里面遇到几个疑惑: # Convert a short name like "sooner" into t ...

  6. Android学习之活动的最佳实践

    •问题的起源 先来模拟一个场景:打开一个 App,最先映入眼帘的是主活动(MainActivity),在该活动中给用户提供了一个 Button, 用户点击该 Button 实现由 MainActivi ...

  7. [转]Android ORM系列之GreenDao最佳实践

    GreenDAO是一个可以帮助Android开发者快速将Java对象映射到SQLite数据库的表单中的ORM解决方案,通过使用一个简单的面向对象API,开发者可以对Java对象进行存储.更新.删除和查 ...

  8. Android 加载GIF图最佳实践

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75578109 本文出自[赵彦军的博客] 起因 最近在项目中遇到需要在界面上显示一个 ...

  9. [转]Android开发最佳实践

    ——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...

随机推荐

  1. 详解k8s零停机滚动发布微服务 - kubernetes

    1.前言 在当下微服务架构盛行的时代,用户希望应用程序时时刻刻都是可用,为了满足不断变化的新业务,需要不断升级更新应用程序,有时可能需要频繁的发布版本.实现"零停机"." ...

  2. Mysql官方文档翻译系列14.18--MySql备份与恢复

    原文链接: (https://dev.mysql.com/doc/refman/5.7/en/innodb-backup-recovery.html) The key to safe database ...

  3. 理解JavaScript中的call和apply方法

    call方法 总的来说call()有这几种作用:1.可以借用另一个对象的方法.2.改变this的指向(重要).3.将arguments数组化.下面详细介绍这三种作用: 1.可以借用另一个对象的方法:当 ...

  4. 开源博客系统使用springmvc

    https://github.com/Zephery/newblog http://www.wenzhihuai.com/index.html

  5. 搭建自己的maven私服 必过

       教你一步一步搭建自己的maven私服 一. 应用场景 有些公司都不提供外网给项目组人员,因此就不能使用maven访问远程的仓库地址,所以很有必要在局域网里找一台有外网权限的机器,搭建nexus私 ...

  6. equals方法的编写建议

    1.显示参数命名为 otherObject ,稍后需要将其转换成另一个叫做 other 的变量. 2.检测 this 与 otherObject 是否引用同一个对象: //这条语句只是一个优化.计算这 ...

  7. IOS 中openGL使用教程4(openGL ES 入门篇 | 离屏渲染)

    通常情况下,我们使用openGL将渲染好的图片绘制到屏幕上,但有时候我们不想显示处理结果,这时候就需要使用离屏渲染了. 正常情况下,我们将屏幕,也就是一个CAEAGLLayer对象作为渲染目标,离屏渲 ...

  8. [Linux]_ELVE_ssh登录远程阿里服务器

    0x00  背景 最近新开了一个服务器,每次都用网页操作太麻烦,索性就用软件登录(貌似界面还有vim支持的也比网页的好),在网上寻找半天,找到一个软件,感觉特别好, 名叫:mobaxterm,好像是免 ...

  9. C#扩展方法(转)

    扩展方法使您能够向现有类型"添加"方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型." 这是msdn上说的,也就是你可以对String,Int,DataRo ...

  10. 0417 jsBom操作+Dom再次整理

    BOM 1.Windows对象 window.open("打开的地址","打开的位置")window.opener:打开此页面的上一个页面对象window.cl ...