對AOP 開發完全不瞭解的同學,請移步到 Android AOP 理解(一)
OK 今天我們來講下AOP 中的APT 開發,通過上一遍我們已經知道目前市面上比較流行的APT 框架有
ButterKnife、Dagger2、DBFlow、AndroidAnnotation、EventBus
其中EventBus 3.x發佈之後其通過註解預編譯的方式解決了之前通過反射機制所引起的性能效率問題,其中註解預編譯所採用的的就是
android-apt的方式,不過Apt工具的作者宣佈了不再維護該工具了,因爲Android Studio推出了官方插件,並且可以通過gradle來簡
單的配置,它就是annotationProcessor,所以我們採用google官方推薦的方案來開發我們的APT項目。
在開始之前,我們要知道什麼是APT:
APT(Annotation Processing Tool)是一種處理註釋的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。
Annotation處理器在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件。
有了這個想法之後,我們來寫個簡單的栗子,比如我們想讓它自動生成如下代碼:
package com.example.helloworld;
import java.lang.String;
import java.lang.System;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
創建Annotation Module
首先,我們需要新建一個名稱爲annotation的Java Library(注意爲什麼這裏創建Android Lib 是因爲android默認不支持javax
包。這個包在我們complier中需要用到。所以這裏我們暫且都採用創建 java Lib
),主要放置一些項目中需要使用到的Annotation和關聯代碼。這裏簡單自定義了一個註解:
package com.apt.andy.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Administrator on 2018/1/24 0024.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TestAnnotation {
}
配置gradle文件:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
創建Compiler Module
創建一個名爲compiler的Java Library,注意,這個是我們的核心,我們自動生成的代碼需要在這裏編寫
在這個module中我們新建一個TestProcessor 繼承於AbstactProcessor(這個類具體幹嘛的後面我們再說)
代碼如下:
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(TestAnnotation.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@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 true;
}
}
上面我們在TestProcessor中重寫了AbstactProcessor中的4個方法,這4個方法的具體作用是什麼?
1.init(ProcessingEnvironment processingEnv)
所有的註解處理器類都必須有一個無參構造函數。然而,有一個特殊的方法init(),它會被註解處理工具調用,以ProcessingEnvironment作爲參 數。ProcessingEnvironment
提供了一些實用的工具類Elements, Types和Filer。
2.process(Set<? extends TypeElement> annoations, RoundEnvironment env)
主要的邏輯處理都在這裏,這個方法裏面可以實現掃描,處理註解,生成 java 文件。使用RoundEnvironment 參數,可以查詢被特定註解標註的元 素。
3.getSupportedAnnotationTypes()
在這個方法裏面你必須指定哪些註解應該被註解處理器註冊。注意,它的返回值是一個String集合,包含了你的註解處理器想要處理的註解類型的全稱
4. getSupportedSourceVersion()
用來指定支持的java版本
在Java 7後多了 SupportedAnnotationTypes 和 SupportedSourceVersion 這個兩個註解用來簡化指定註解和java版本的操作:
也就是說可以改成這樣:
@SupportedAnnotationTypes({"com.apt.andy.annotation.TestAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@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 true;
}
}
其中SupportedAnnotationTypes 如果支持多個註解,中間用逗號隔開
gradle 配置如下:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.7.0'
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile project(':annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
然後在App中直接用註解就可以:
package com.apt.andy.myfirstaptdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.apt.andy.annotation.TestAnnotation;
@TestAnnotation
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
App gradle配置如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.apt.andy.myfirstaptdemo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile project(':annotation')
annotationProcessor project(':compile')
}
然後Rebuild下,就可以在App build/generated/source/apt/debug 目錄中看到我們要生成的類了。