越來越多第三方庫使用apt技術串结,如DBflow、Dagger2饶套、ButterKnife、ActivityRouter其馏、AptPreferences凤跑。在編譯時根據Annotation生成了相關的代碼,非常高大上但是也非常簡單的技術叛复,可以給開發(fā)帶來了很大的便利仔引。
Annotation
如果想學習APT,那么就必須先了解Annotation的基礎褐奥,這里就不展開了
APT
APT(Annotation Processing Tool)是一種處理注釋的工具,它對源代碼文件進行檢測找出其中的Annotation咖耘,使用Annotation進行額外的處理。
Annotation處理器在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件撬码,將它們一起生成class文件儿倒。
創(chuàng)建Annotation Module
首先,我們需要新建一個名稱為annotation的Java Library,主要放置一些項目中需要使用到的Annotation和關聯(lián)代碼夫否。這里簡單自定義了一個注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test { }
配置build.gradle,主要是規(guī)定jdk版本
apply plugin: 'java'
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
創(chuàng)建Compiler Module
創(chuàng)建一個名為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')
}
- 定義編譯的jdk版本為1.7微谓,這個很重要森篷,不寫會報錯。
- AutoService 主要的作用是注解 processor 類豺型,并對其生成 META-INF 的配置信息仲智。
- JavaPoet 這個庫的主要作用就是幫助我們通過類調用的形式來生成代碼。
- 依賴上面創(chuàng)建的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也沒有實際用上抖韩,下面我們做一些更加實際的代碼生成。實現(xiàn)基于注解的View疫铜,代替項目中的findByView
茂浮。這里僅僅是學習怎么用APT,如果真的想用DI框架壳咕,推薦使用ButterKnife席揽,功能全面。
- 第一步谓厘,在annotation module創(chuàng)建@DIActivity幌羞、@DIView注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
int value() default 0;
}
- 創(chuàng)建DIProcessor方法
@AutoService(Processor.class)
public class DIProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public Set<String> getSupportedAnnotationTypes() {
// 規(guī)定需要處理的注解
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;
}
}
- 使用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子類
- 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<? extends Element> members = processingEnv.getElementUtils().getAllMembers(typeElement);
總結
推薦閱讀dagger2读规、dbflow罩旋、ButterKnife等基于apt的開源項目代碼罩缴。JavaPoet 也有很多例子可以學習。
Example代碼
https://github.com/taoweiji/DemoAPT
我們的開源項目推薦:
Android快速持久化框架:AptPreferences
AptPreferences是基于面向對象設計的快速持久化框架聂宾,目的是為了簡化SharePreferences的使用,減少代碼的編寫诊笤∠敌常可以非常快速地保存基本類型和對象讨跟。AptPreferences是基于APT技術實現(xiàn)纪他,在編譯期間實現(xiàn)代碼的生成鄙煤,根據不同的用戶區(qū)分持久化信息。
https://github.com/joyrun/AptPreferences
ActivityRouter路由框架:通過注解實現(xiàn)URL打開Activity
基于apt技術茶袒,通過注解方式來實現(xiàn)URL打開Activity功能馆类,并支持在WebView和外部瀏覽器使用,支持多級Activity跳轉弹谁,支持Bundle乾巧、Uri參數注入并轉換參數類型。