APT-概念了解
友情鏈接:
https://lizhaoxuan.github.io/2016/07/17/apt-Grammar-explanation/
https://github.com/Gavin-ZYX/APTTest.git
apt
APT(Annotation Processing Tool)是一種處理注釋的工具残吩,它對源代碼文件進行檢測找出其中的Annotation袖迎,根據(jù)注釋自動生成代碼顿仇。Annotation處理器在出來Annotation時可以根據(jù)源文件中的Annotation生成額外的源文件和其它的文件(文件具體內(nèi)容由Annotation處理器的編寫者決定)东抹,APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件。
annotationProcessor
annotationProcessor是APT工具中的一種,他是google開發(fā)的內(nèi)置框架,不需要引入胶坠,可以直接在build.gradle文件中使用
android-apt
android-apt是由一位開發(fā)者自己開發(fā)的apt框架,源代碼托管在這里繁堡,隨著Android Gradle 插件 2.2 版本的發(fā)布沈善,Android Gradle 插件提供了名為 annotationProcessor 的功能來完全代替 android-apt ,自此android-apt 作者在官網(wǎng)發(fā)表聲明最新的Android Gradle插件現(xiàn)在已經(jīng)支持annotationProcessor椭蹄,并警告和或阻止android-apt 闻牡,并推薦大家使用 Android 官方插件annotationProcessor。
Demo-知識點
注解
@Retention
- @Retention(RetentionPolicy.SOURCE) 源碼時注解绳矩,一般用來作為編譯器標記罩润。就比如Override, Deprecated, SuppressWarnings這樣的注解
- @Retention(RetentionPolicy.RUNTIME) 運行時注解,一般在運行時通過反射去識別的注解
- @Retention(RetentionPolicy.CLASS) 編譯時注解翼馆,在編譯時處理
@Target
- @Target(ElementType.TYPE) 接口割以、類、枚舉应媚、注解
- @Target(ElementType.FIELD)字段严沥、枚舉的常量
- @Target(ElementType.METHOD) 方法
- @Target(ElementType.PARAMETER) 方法參數(shù)
- @Target(ElementType.CONSTRUCTOR) 構(gòu)造函數(shù)
- @Target(ElementType.LOCAL_VARIABLE) 局部變量
- @Target(ElementType.ANNOTATION_TYPE) 注解
- @Target(ElementType.package) 包
@Inherited
該注解的字面意識是繼承,但你要知道注解是不可以繼承的中姜。
ie:當你的注解定義到類A上消玄,此時跟伏,有個B類繼承A,且沒使用該注解翩瓜。但是掃描的時候受扳,會把A類設置的注解,掃描到B類上
輸出Log
Messager
//取得Messager對象
Messager messager = processingEnv.getMessager();
Processor日志輸出的位置在編譯器下方的Messages窗口中
Processor支持最基礎(chǔ)的System.out方法
同樣Processor也有自己的Log輸出工具: Messager
同Log類似兔跌,Messager也有日志級別的選擇
- Diagnostic.Kind.ERROR
- Diagnostic.Kind.WARNING
- Diagnostic.Kind.MANDATORY_WARNING
- Diagnostic.Kind.NOTE
- Diagnostic.Kind.OTHER
Element
Represents a program element such as a package, class, or method.
Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine).
表示一個程序元素勘高,比如包、類或者方法
ExecutableElement
表示某個類或接口的方法坟桅、構(gòu)造方法或初始化程序(靜態(tài)或?qū)嵗┗ㄗ⑨岊愋驮亍?/p>
對應@Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)
PackageElement;
表示一個包程序元素。提供對有關(guān)包極其成員的信息訪問桦卒。
對應@Target(ElementType.PACKAGE)
TypeElement;
表示一個類或接口程序元素。提供對有關(guān)類型極其成員的信息訪問匿又。
對應@Target(ElementType.TYPE)
注意:枚舉類型是一種類方灾,而注解類型是一種接口。
TypeParameterElement;
表示一般類碌更、接口裕偿、方法或構(gòu)造方法元素的類型參數(shù)。
對應@Target(ElementType.PARAMETER)
VariableElement;
表示一個字段痛单、enum常量嘿棘、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù)旭绒。
對應@Target(ElementType.LOCAL_VARIABLE)
修飾方法的注解和ExecutableElement
當你有一個注解是以@Target(ElementType.METHOD)定義時鸟妙,表示該注解只能修飾方法。
那么這個時候你為了生成代碼挥吵,而需要獲取一些基本信息:包名重父、類名、方法名忽匈、參數(shù)類型房午、返回值。
//OnceClick.class 以 @Target(ElementType.METHOD)修飾
for (Element element : roundEnv.getElementsAnnotatedWith(OnceClick.class)) {
//對于Element直接強轉(zhuǎn)
ExecutableElement executableElement = (ExecutableElement) element;
//非對應的Element丹允,通過getEnclosingElement轉(zhuǎn)換獲取
TypeElement classElement = (TypeElement) element
.getEnclosingElement();
//當(ExecutableElement) element成立時郭厌,使用(PackageElement) element
// .getEnclosingElement();將報錯。
//需要使用elementUtils來獲取
Elements elementUtils = processingEnv.getElementUtils();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//全類名
String fullClassName = classElement.getQualifiedName().toString();
//類名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//方法名
String methodName = executableElement.getSimpleName().toString();
//取得方法參數(shù)列表
List<? extends VariableElement> methodParameters = executableElement.getParameters();
//參數(shù)類型列表
List<String> types = new ArrayList<>();
for (VariableElement variableElement : methodParameters) {
TypeMirror methodParameterType = variableElement.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
}
//參數(shù)名
String parameterName = variableElement.getSimpleName().toString();
//參數(shù)類型
String parameteKind = methodParameterType.toString();
types.add(methodParameterType.toString());
}
}
修飾屬性雕蔽、類成員的注解和VariableElement
當你有一個注解是以@Target(ElementType.FIELD)定義時折柠,表示該注解只能修飾屬性、類成員批狐。
那么這個時候你為了生成代碼液走,而需要獲取一些基本信息:包名、類名、類成員類型缘眶、類成員名
如何獲戎龈:
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
//ElementType.FIELD注解可以直接強轉(zhuǎn)VariableElement
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) element
.getEnclosingElement();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//類名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//類成員名
String variableName = variableElement.getSimpleName().toString();
//類成員類型
TypeMirror typeMirror = variableElement.asType();
String type = typeMirror.toString();
}
修飾類的注解和TypeElement
當你有一個注解是以@Target(ElementType.TYPE)定義時,表示該注解只能修飾類巷懈、接口该抒、枚舉。
那么這個時候你為了生成代碼顶燕,而需要獲取一些基本信息:包名凑保、類名、全類名涌攻、父類欧引。
如何獲取:
for (Element element : roundEnv.getElementsAnnotatedWith(xxx.class)) {
//ElementType.TYPE注解可以直接強轉(zhuǎn)TypeElement
TypeElement classElement = (TypeElement) element;
PackageElement packageElement = (PackageElement) element
.getEnclosingElement();
//全類名
String fullClassName = classElement.getQualifiedName().toString();
//類名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//父類名
String superClassName = classElement.getSuperclass().toString();
}
Demo
module
apt-annotation (java-library)
apt-processor (java-library)
apt-library (com.android.library)
apt-annotation
注解類BindView(編譯時注解)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
apt-processor
build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt-annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
AbstractProcessor
@SupportedAnnotationTypes({"com.example.gavin.apt_annotation.BindView"})
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
添加自己需要處理的注解恳谎,可以通過兩種方式:getSupportedAnnotationTypes()或者直接用注解
@SupportedAnnotationTypes("全路徑")
process方法
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
// 1.會執(zhí)行多次芝此,所以要先clear
mProxyMap.clear();
// 2.得到所有的注解并收集到map中
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
//elements的信息保存到mProxyMap中
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
proxy.putElement(id, variableElement);
}
// 3.通過遍歷mProxyMap,創(chuàng)建java文件 通過javapoet生成
for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
注:APT有一個局限性因痛,就是只會掃描Java源碼婚苹,不會掃描jar ,aar 和class 鸵膏。也就是說膊升,所有模塊需要以源碼形式存在。而現(xiàn)在通用的做法是谭企,將模塊打包成jar或者aar廓译,發(fā)布到Maven庫,再由其他模塊自行引用债查,解決這個問題的基本方法用gradle plugin方法(項目中的medusa庫就是插件的方式)