簡介
APT(Annotation Processing Tool)是一種處理注釋的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。 Annotation處理器在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件。
這里面有幾個關鍵字:處理注解,編譯生成,我們總結一句話就是APT能夠在編譯期通過處理注解,生成我們想要的文件。
注解處理器是 javac 自帶的一個工具,用來在編譯時期掃描處理注解信息。你可以為某些注解注冊自己的注解處理器。這里,我假設你已經了解什么是注解及如何自定義注解。如果你還未了解注解的話,可以查看官方文檔。注解處理器在 Java 5 的時候就已經存在了,但直到 Java 6 (發布于2006看十二月)的時候才有可用的API。過了一段時間java的使用者們才意識到注解處理器的強大。所以最近幾年它才開始流行。
一個特定注解的處理器以 java 源代碼(或者已編譯的字節碼)作為輸入,然后生成一些文件(通常是.java文件)作為輸出。那意味著什么呢?你可以生成 java 代碼!這些 java 代碼在生成的.java文件中。因此你不能改變已經存在的java類,例如添加一個方法。這些生成的 java 文件跟其他手動編寫的 java 源代碼一樣,將會被 javac 編譯
用法
工程目錄結構如下:
annotation 是我們存放注解的地方。
compiler 使我們處理注解的地方。
api 是我們提供對外調用的api。
現在有這個需求只要在我們的類上加了@Test的注解就會生成
package com.delta.aptlearning;
import java.lang.String;
import java.lang.System;
public class MainActivity$$helloWorld {
public static void main(String[] args) {
System.out.println("app");
}
}
- 創建注解libary annotation里面注解如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test {
}
我們@target是Type表明該注解只能注解類獲接口
- 創建compiler libary,因為我們的AbstractProcesso這個類是屬于java不需要android一些插件所以我們可以創建javalibary。buildgradle如下:
apply plugin: 'java'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
compile project(':annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
- 定義編譯版本jdk為1.7
- AutoService主要的作用是注解processor類,并對其生成META—INF的配置信息(這里可以不用這個,也可以按照原始方式進行注解)
- javapoet主要的作用是幫助我們通過類調用的形式生成代碼(也可以用string類型的方式進行拼接)
- annotaion是我們要依賴的使用的注解庫
。
- 處理注解。這時候我們要用到abstractProcessor類,我們看下api
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
init(ProcessingEnvironment processingEnv) :所有的注解處理器類都必須有一個無參構造函數。然而,有一個特殊的方法init(),它會被注解處理工具調用,以ProcessingEnvironment作為參數。ProcessingEnvironment 提供了一些實用的工具類Elements, Types和Filer。我們在后面將會使用到它們。
process(Set<? extends TypeElement> annoations, RoundEnvironment env) :這類似于每個處理器的main()方法。你可以在這個方法里面編碼實現掃描,處理注解,生成 java 文件。使用RoundEnvironment 參數,你可以查詢被特定注解標注的元素(原文:you can query for elements annotated with a certain annotation )。
-
getSupportedAnnotationTypes():在這個方法里面你必須指定哪些注解應該被注解處理器注冊。注意,它的返回值是一個String集合,包含了你的注解處理器想要處理的注解類型的全稱。換句話說,你在這里定義你的注解處理器要處理哪些注解。注意這里也可以用注解的方式來實現
eg: @SupportedAnnotationTypes("com.delta.annotationmodule.Test")
etSupportedSourceVersion() : 用來指定你使用的 java 版本,注意此處也可以用注解的方式來實現
eg:@SupportedSourceVersion(SourceVersion.RELEASE_6)
==這個內容會抽出一片文章詳細介紹==
-
注冊注解
你可能會問 “怎樣注冊我的注解處理器到 javac ?”。你必須提供一個.jar文件。就像其他 .jar 文件一樣,你將你已經編譯好的注解處理器打包到此文件中。并且,在你的 .jar 文件中,你必須打包一個特殊的文件javax.annotation.processing.Processor到META-INF/services目錄下第一種方案
在main文件夾下創建resources文件夾
在resources資源文件夾下創建META-INF文件夾
然后在META-INF文件夾中創建services文件夾
-
然后在services文件夾下創建名為javax.annotation.processing.Processor的文件,在該文件中配置需要啟用的注解處理器,即寫上處理器的完整路徑,有幾個處理器就寫幾個,分行寫幺,比如我們這里是:com.example.TestProcessor
第二種方案
在buildGradle文件中我們要加入 compile 'com.google.auto.service:auto-service:1.0-rc2'
-
在我們的注解處理器上加上
@AutoService(Processor.class) public class TestProcessor extends AbstractProcessor
app用法
最終我們生成的注解需要在我們的android工程里面去應用怎么做呢?
這時候要用到Android-apt,那么問題來來了什么是android apt?
首先我們先看幾個問題?
1. 首先注解處理器也就是我們的compile不應該打包到我們的apk中增加體積。
2. 生成的文件怎么被android studio引用
3. 怎么從buildgradle里向我們的注解處理器傳遞參數
Anroid-apt是用在Android Studio中處理注解處理的插件。他的作用如下
- 只允許配置編譯時注解處理器依賴,但在最終APK或者Library中不包含注解處理器的代碼。
apt project(':compiler')
-
這個插件可以自動的幫你為生成的代碼創建目錄,使注解處理器生成的代碼能被Android Studio正確的引用,讓生成的代碼編譯到APK里面去
圖片.png - 從buildGradle里向我們的注解處理器傳遞參數
apt{
arguments{
module "app"
}
}
處理器接收
Map<String, String> options = processingEnvironment.getOptions();
Set<Map.Entry<String, String>> entries = options.entrySet();
for (Map.Entry<String, String> entry : entries) {
ss = entry.getValue() + ss;
messager.printMessage(Diagnostic.Kind.NOTE, entry.getKey() + "----" + entry.getValue());
}
老版本的用法
整個工程的buildGradle你需要
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
而在你的app中需要這樣
apply plugin: 'com.neenbedankt.android-apt'
dependencies{
compile project(':annotation')
apt project(':compiler')
compile project(':api')
}
到這里也許該松口氣了,但是不好的消息來了android-apt插件作者近期已經發表聲明表示后續不會再繼續維護該插件。但也有個好消息是
Gradle從2.2版本開始支持annotationProcessor功能來代替Android-apt。另外,和android-apt只支持javac編譯器相比,annotationProcessor同時支持javac和jack編譯器
具體怎么用呢?
我們只需要在我們的app中這樣加入
dependencies {
compile project(':annotation')
compile project(':api')
annotationProcessor project(":compiler")
}
是不是很簡單,如果你要傳遞參數你需要這樣
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
很方便調用但是有個前提==你的Gradle版本是2.2.X以上,就可以替換掉Android-apt。==
工程目錄結構推薦
由于編譯時注解處理器只在編譯過程中使用,因此我們不希望注解處理器相關的代碼在最終的APK中存在,這樣能夠有效的較少方法數。比如我通常在編寫注解Annotation Processor的時候會引用javapoet和Guava,如果將這些代碼也打進最終的APK中會造成方法數的暴增,因此建議將注解處理器相關代碼單獨成為一個模塊。
另外為了方面注解被其他工程引用,通常我也建議將注解的定義單獨劃分成一個模塊。
綜上,我們最終的項目結構如下:
xxx/xxx-api:主工程/提供api,Android Library類型
xx-compiler:注解處理器模塊,Java Library類型,打包apk時可以不要
xxx-annotations:自定義注解,Java Library類型,打包apk時可以不要
xxx/xxx-api依賴xxx-annotations,xxx-compiler依賴xxx-annotations。