android-apt 使用
前言
APT的概念大家應該不會陌生, 而且在很多第三方庫中都有使用到, 最有名的應該就是ButterKnife了. 這裡基礎概念就略過了, 本篇主要是著重在怎麼編寫自己的註解處理器, 以及一些踩到的坑.
開始
一般要實現編譯器註解處理生成, 需要新建兩個module, 分別存放自定義的Annotation和對應Annotation的處理器.
自定義註解
我們先新建存在自定義註解的module, 注意,這裡建議新建java-library, 便於本地除錯時給存放處理器的module依賴使用 , 對應gradle配置如下
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } sourceCompatibility = "1.8" targetCompatibility = "1.8"
自定義一個新的註解
@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface TestAnnotation { String value(); }
-
這裡
Retention
註解表示設定註解保留時機, 需要傳遞的是RetentionPolicy
列舉型別, 值分別有:SOURCE CLASS RUNTIME
-
Target
表示註解適用的上下文, 即他的目標修飾型別, 可以傳陣列,值應該為ElementType,列舉各個值的含義可以看官方文件, 我們主要用到比較多的應該是TYPE METHOD FIELD LOCAL_VARIABLE CONSTRUCTOR
註解處理器
同樣需要新建一個java-library, 對應gradle的配置如下
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // 協助我們生成類檔案 implementation 'com.squareup:javapoet:1.11.0' // 自定義註解的庫 implementation project(':anno') // 協助自動註冊META-INF implementation 'com.google.auto.service:auto-service:1.0-rc4' }
然後開始編寫處理器, 關於如何使用JavaPoet
, 建議看下官方文件, 在這裡不再細說.最後通過Filer
來進行檔案的寫入.
// AutoService註解協助自動生成META-INF服務, 提供專案識別自定義的註解處理器 @AutoService(Processor.class) public class TestProcessor extends AbstractProcessor { private Filer mFiler; private Messager messager; /** * init()方法可以初始化拿到一些使用的工具,比如檔案相關的輔助類 Filer;元素相關的輔助類Elements;日誌相關的輔助類Messager; * @param processingEnv */ @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mFiler = processingEnv.getFiler(); messager = processingEnv.getMessager(); } /** * @return 返回Java版本 * 也可以通過@SupportedSourceVersion來指定, 如果沒有設定預設返回的是JDK1.6版本 */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } /** * * @return 支援的註解型別 * 即是這個處理器是需要註冊在哪幾個註解上的, 也可以通過@SupportedAnnotationTypes來指定 */ @Override public Set<String> getSupportedAnnotationTypes() { LinkedHashSet<String> types = new LinkedHashSet<>(); types.add(TestAnnotation.class.getCanonicalName()); return types; } /** * 一個Processor的main函式 * @param annotations 請求被處理的註解 * @param roundEnv 可以用來查詢特定註解的被註解元素 * @return true 被當前處理器處理; false 可能被其他同樣宣告支援對應註解的處理器用來處理 */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { HashMap<String, String> nameMap = new HashMap<>(); Set<? extends Element> annotationElements = roundEnv.getElementsAnnotatedWith(TestAnnotation.class); for (Element element: annotationElements ) { TestAnnotation annotation = element.getAnnotation(TestAnnotation.class); String name = annotation.value(); nameMap.put(name, element.getSimpleName().toString()); } generateJavaFile(nameMap); return true; } private void generateJavaFile(Map<String, String> nameMap){ // 通過javaPoet生成java檔案 } }
錯誤資訊
由於註解處理器是JVM在編譯期進行執行, 所以普通的Log無法用來提示我們來列印一些日誌或者用來提示錯誤資訊.在Processor
中, 當執行初始化的時候, 會傳進來一個ProcessingEnvironment
引數, 在上方程式碼註釋內我也寫了, 他會提供一些我們需要的引數, 比如Messager
一個用來報告錯誤, 警報或者其他通知的工具, 它可以用來提醒第三方使用註解的開發者們來處理相關的錯誤.它有多個過載函式, 用於設定提醒到哪個地步, 具體可以自己嘗試下.
public interface Messager { void printMessage(Diagnostic.Kind kind, CharSequence msg); void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e); void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a); /** * @param kind 通知型別 * @param msg內容 * @param e註解元素 * @param a包含註解的值得註解 * @param v提示到註解的值使用位置 */ void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v); }
使用自定義註解
當我們需要使用的時候, 那麼就跟常見的幾個第三方庫的使用(比如[Dagger之類
])是一樣的.
// 依賴管理自定義註解的庫 implementation project(':anno') // apt配置註解處理庫 annotationProcessor project(':aptlib')
值得注意的是如果你使用的是kotlin開發使用到對應的註解, 那麼首先需要依賴kapt外掛, 然後以kapt
替換annotationProcessor
添加註解處理庫, 當專案裡有Java檔案使用到註解的時候, kapt也會兼顧到.
apply plugin: 'kotlin-kapt' dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':anno') kapt project(':aptlib') }
Tips
我們在開始的時候, 談到註解的宣告和處理器需要分別放在不同的module裡, 原因是因為, 如果放在一個module裡, 那麼應用專案在依賴的時候, 就會變成
implementation project(':aptlib') annotationProcessor project(':aptlib')
而不論是我們使用的AbstractProcessor
還是JavaPoet
庫, 都是依賴於JDK進行編譯的, 當應用專案依賴於(implementation
)這個庫的時候, AS就會預設用SDK來進行編譯, 導致編譯器提示部分類無法載入, 所以我們才需要分成兩個module, 保證到進行邏輯處理的處理器可以不會通過implementation
被依賴進專案中.相關可以看看相關的issue的說明