--- title: 'Java 註解' disqus: kyleAlien --- Java 註解 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile: > Java 註解從 Java 1.5 添加到 Java,以下會使用 Android 註解 & Java 註解 :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**深入探討 Android、Java 註解應用:分析註解與 Enum 的差異 | Android APT**](https://devtechascendancy.com/android-annotations-enums-guide/) ::: [TOC] ## 註解概述 Support Annotation Library 從 19.1 引進全新的函數套件,在開發中加入程式可**提升程式的品質** (可讓編譯器檢查),增加程式碼的健壯 ## Android 基礎註解 * Android 中自身有的註解介紹 ```java= // Android gradle 加入註解的依賴 dependencies { implementation 'com.android.support:support-annotations:28.0.0' } ``` ### Nullness 註解 | 註解名 | 意義 | | --------- | ---------------------------- | | @Nullable | 參數 or 回傳值**可以為空** | | @NonNull | 參數 or 回傳值**不可以為空** | ```java= public class TestAnnotation { public static void main(String... args) { String s1 = useAnnotation("Alien"); String s = useAnnotation(null); } private static @NonNull String useAnnotation(@NonNull String str) { return "Hello World, " + str; } } ``` **--實作--** > ![](https://i.imgur.com/bUrLszu.png) ### 資源類型註解 * 資源在 Android 中通常以整數來代表 (存在於 R.java 中),該註解可以避免資源被放置到錯的參數中 | 註解名 | 意義 | | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | @XXXRes | XXX 代表為 android.R.XXX 類型的資源,有 Animator, Anim, String, Array, Bool, Color, Drawable, Integer, Layout, ID...**+Res** | ```java= public class TestAnnotation { public static void main(String... args) { TestRxxx(new Integer[]{}); TestRxxx(new String[]{}); TestRxxx(R.id.response); } private static void TestRxxx(@IdRes int id) { } } ``` **--實作--** > ![](https://i.imgur.com/qlvVg1l.png) ### 執行緒註解 | 註解名 | 意義 | | ------------- | --------------------------------------------------------------------------------------------------------------------- | | @UiThread | 一個應用而言可能存在多個 UiThread,每個 UI 執行緒對應不同視窗 | | @MainThread | 標記**執行在主線程**,一個應用只有一個主執行緒 (就是 @UiThread 執行緒),大部分情況 @MainThread 使用在生命週期相關函數 | | @WorkThread | 背景**後台工作**的執行緒 | | @BinderThread | 標記**執行在 Binder** 的執行緒 | * `@UiThread` & `@MainThread` 一般來說是可以互換的,因為 UI Thread 通常就是指 Main Thrad(並非一定) > 像是 Android 的輕量級異步執行緒框架 `AnsyncTask` 就有此用這些 … 有關於 AnsyncTask 解釋請點擊連結了解([**Template 設計模式 | 實現與解說 | Android source AsyncTask 微框架**](https://devtechascendancy.com/object-oriented_design_template/)) ```java= // AnsyncTask.java @WorkerThread protected abstract Result doInBackground(Params... params); ... @MainThread protected void onPreExecute() { } ... @SuppressWarnings({"UnusedDeclaration"}) @MainThread protected void onPostExecute(Result result) { } ... @SuppressWarnings({"UnusedDeclaration"}) @MainThread protected void onProgressUpdate(Progress... values) { } ... @SuppressWarnings({"UnusedParameters"}) @MainThread protected void onCancelled(Result result) { onCancelled(); } ``` ### RGB 註解 * RGB / ARGB 每個字母佔 8Byte > ![](https://i.imgur.com/z1AjztO.png) * 它跟 @ColorRes 的差別在 **@ColorRes 只接收 Resouce 的資源項目,@ColorInt 接收所有符合的資源內容** | 註解名 | 意義 | | --------- | ---------------------------- | | @ColorInt | 需要傳入 RGB 顏色**整數** | **--實作--** > 只要符合 `@ColorInt` 規範的整數就可以 (以下可以看出 ID 類的標明也是符合,但整數 20 不可以) > > ![](https://i.imgur.com/sBfx9gV.png) ### 註解範圍 | 註解名 | 意義 | Example | | ----------------- | ----------------------------- | --------------------------- | | @Size(min=x) | 集合**下限** | @Size(min=1) | | @Size(max=x) | 集合**上限** | @Size(max=3) | | @Size(value=x) | **固定**集合數量 | @Size(value=2) | | @Size(multiple=x) | 集合大小可為 x 的**倍數數量** | @Size(multiple=2) | | @IntRange(x) | 參數類型可為 int or long | @IntRange(from=0,to=255) | | @FloatRange(x) | 參數類型可為 float or double | @FloatRange(from=-1.0,to=1.0) | ```java= public class TestAnnotation { public static void main(String... args) { TestSizeMin(new int[]{1,2}); TestSizeMin(new int[]{}); TestSizeMax(new int[]{1,2}); TestSizeMax(new int[]{1,2,3,4}); TestSizeFixed(new int[]{1,2}); TestSizeFixed(new int[]{1,2,3}); TestSizeMultiple(new int[]{1,2}); TestSizeMultiple(new int[]{1,2,3,4,5}); TestIntRange(123); TestIntRange(256); TestFloatRange(1.0F); TestFloatRange(-2.0F); } private static void TestSizeMin(@Size(min = 1) int[] params) { } private static void TestSizeMax(@Size(max = 3) int[] params) { } private static void TestSizeFixed(@Size(value = 2) int[] params) { } private static void TestSizeMultiple(@Size(multiple = 2) int[] params) { } private static void TestIntRange(@IntRange(from = 0, to = 255) long a) { } private static void TestFloatRange(@FloatRange(from = -1.0, to = 1.0) float a) { } } ``` * 限定於**陣列**才可判斷,其餘不可判斷 ```java= public class TestAnnotation { public static void main(String... args) { ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); TestSizeInList(list); TestSizeOther(1,3,5,6,7,8); } private static void TestSizeInList(@Size(3) ArrayList<Integer> list) { } private static void TestSizeOther(@Size(3) int... params) { } } ``` **--實作--** > ![](https://i.imgur.com/aO7Fm9t.png) ### 許可權註解 | 註解名 | 意義 | | -------------------------------- | ----------------------------------------------- | | @RequiresPermission() | 提醒使用者要在 AndroidManifest.mxl 中宣告許可權 | | @RequiresPermission(allof={x,y}) | 以下的權限皆需要 | | @RequiresPermission(anyOf={x,y}) | 最少需要其中一個 | ### 重新定義函數註解 * Android 的生命週期 onCreate 就有使用;**建構函數不需要** @CallSuper | 註解名 | 意義 | | --------- | ---------------------------- | | @CallSuper | 提醒使用者要複寫該方法 | ```java= public class TestAnnotation { public static void main(String... args) { new RichMan(new PoorMan()).wear(); } } abstract class Decorator { Decorator() { } public abstract void wear(); } class PoorMan extends Decorator { @Override public void wear() { System.out.println("UnderPant"); } } abstract class WorkMan extends Decorator { protected Decorator d; protected WorkMan(Decorator d) { this.d = d; } @CallSuper @Override public void wear() { d.wear(); } } class RichMan extends WorkMan { protected RichMan(Decorator d) { super(d); } @Override public void wear() { super.wear(); System.out.println("Jacket"); System.out.println("Pant"); System.out.println("T-shirt"); System.out.println("sock"); System.out.println("shoes"); } } ``` 以 [**裝飾器模式**](https://hackmd.io/DlDU-niGRg-0p-dw-BqxKQ)來說每個實作類都需要呼叫該父類的方法,但使用者有時會忘記呼叫 `super()` 這時就可以使用,用來提醒使用框架者要記得呼叫父類方法 :::warning 這種提醒機制並「**非強制**」,就算使用者不遵從提醒仍可編譯過 ::: **--實作--** > ![](https://i.imgur.com/LQqCzLw.png) ### 回傳值註解 * 如果某函數需要對 `return` 值作處理這時就可以呼叫 @CheckResult,把要寫的警訊寫在 suggest 中,Android 第三方 Lib 很常使用 | 註解名 | 意義 | | ------------------------- | ---------------------- | | @CheckResult(suggest="x") | 提醒使用者要注意回傳值 | ## Enum 與 Annotation 差異 我們在撰寫程式時,常常使用 Enum 來表達出可使用的選項(同時限制使用者只能使用 Enum 中的選項);而使用註解也可以達到告訴使用者該參數能接收的選項 > 同樣注意到的是,這並非是一個硬式限制,更像是一種提醒 ### Enum 反編譯解析 * **Enum 就算是一個「類」,而一個類所占用的標頭最少是 12 個字節(byte),所以會耗費更多的記憶體空間** ```java= class MyTestClass { enum Level { LEVEL_1, LEVEL_2, LEVEL_3, LEVEL_4, LEVEL_5, LEVEL_6, } } ``` 上面程式碼經過 ASM 反組譯後會變為,**==每一個成員皆為一個 enum 類==**,所 **佔用空間過大** (Byte 描述 Class 會耗費多一點空間) > > ![](https://i.imgur.com/lu5g8kE.png) ### Enum vs. Android IntDef * `@IntDef` 註解由 Androidx 提供,**並標明 `@Target({==ANNOTATION_TYPE==})` 表示它是註解在註解上的註解** **InDef 可使用在 IDE 檢查,其階段只保留到 ++Source++**,以下是它的源碼 > Android 還有 `StringDef`... 等等可以使用 ```java= // 源碼 @Retention(SOURCE) // 保存到 Source 階段 @Target({ANNOTATION_TYPE}) public @interface IntDef { /** Defines the allowed constants for this element */ int[] value() default {}; boolean flag() default false; boolean open() default false; } ``` * **`@IntDef` 特性有**: 1. **效能更高**: 在 Android 平台上,使用 enum 會比使用 int 消耗更多的內存,因為 enum 會引入額外的方法和屬性;使用 `@IntDef` 則不會有這個問題,因為它本質上還是使用整數值 2. **編譯期檢查**: `@IntDef` 提供了編譯期檢查,確保只能使用指定的整數值,避免了使用其他不合法的值 3. **提高可讀性**: 通過 `@IntDef` 註解,可以使代碼更加易讀,因為我們可以定義一組有意義的常量來表示不同的狀態或類型 * **`@IntDef` 的使用方式**: 要使用 **IntDef 需要自定義一個註解,並且標註該註解作用的區域 @Target,還有它的保留階段 `@Retention`** ```java= class MyTestClass { //"1. " @Target({ElementType.FIELD, ElementType.PARAMETER}) @IntDef(value = {LEVEL_1, LEVEL_2, LEVEL_3, LEVEL_4, LEVEL_5}) @Retention(RetentionPolicy.SOURCE) @interface MyLevel { } public static final int LEVEL_1 = 1; public static final int LEVEL_2 = 2; public static final int LEVEL_3 = 3; public static final int LEVEL_4 = 4; public static final int LEVEL_5 = 5; public static void main(String []args){ System.out.println("Hello World"); setLevel(1111); //"2. " setLevel(3); setLevel(LEVEL_3); } public static void setLevel(@MyLevel int level) { System.out.println("level: " + level); } } ``` 1. **使用自定義註解,要標示它作用的區域**,如果沒有標示 `ElementType.PARAMETER` 該註解則不能作用於函式參數 2. 放入**非標準的格式++編譯仍然可以過++,但是寫程式時跳出警告訊息,==讓程式更加健壯==** > 可看出所占用的空間變少,一個標示只佔 4 個字節 > > ![](https://i.imgur.com/sA9LFOw.png) ## Java標準註解 * Java 也可使用,不限定 Android,它定義在 `java.lang`、`java.lang.annotation`、`javax.annotation` 套件中 ### 編譯相關註解:SafeVarargs 使用 | 註解名 | 意義 | | ------------------------------ | ---------------------------------------------- | | @Override | **覆寫**父類方法,如果並無覆寫則會報錯誤 | | @Deprecated | 已**遺棄**的方法,編譯器會警告使用者 | | @SuppressWarnings(value={x,y}) | **抑制**編譯器警告,可抑制多個警告 | | @SafeVarargs | **支援資料可變長度** | | @Generated | 一般**用於給程式自動產生代碼**,不建議手動修改 | | @FuntionalInterface | 該**介面只有一個方法**,可使用在 Lambda | * **註解 `@SafeVargs` 目的 & 意義** 其主要目的是處理 **==泛型 的可變長參數(T...t)==** ,告編譯器此泛型是**安全長參數** 可變長參數使用數組儲存,而數組 & 泛型不能很好的混合使用 :::warning * **數組 & 泛型 ?** 可以使用泛型數組不會有警告 **數組**:編譯期間就已確定(靜態確認) **泛型**:運行時才能確定數據,因此編譯時無法判別其正確性,導致產生警告(動態確認)… 這時我們就可以使用 `@SafeVargs` 去壓制警告 ::: :::danger * **註解 `@SafeVargs` 只能宣告在 ==static== or ==final== 的函數** > ![](https://i.imgur.com/9SYY4HC.png) ::: * 對於 `SafeVarargs`、`SuppressWarnings` 使用範例如下 ```java= public class TestProblemOfGeneric { public static void main(String[] args) { List<String> list1 = new ArrayList<>(); List<String> list2 = new ArrayList<>(); //"2. " FixList.add2List(list1, "Pan", "Kyle", "Alien"); FixList.add2List(list2, "One", "Two", "Three"); List<List<String>> list3 = new ArrayList<>(); //"3-1. " FixList.add2List(list3, list1, list2); //"3-2. " FixList.TestMethod(Arrays.asList("Hello!"), Arrays.asList("World!")); } } class FixList { public static <T> void add2List(List<T> list, T...data) { for(T t : data) { list.add(t); } } //"1. " public static void TestMethod(List<String>...list) { Object[] o = list; //"4. " o[0] = Arrays.asList(new Integer(123)); String str = list[0].get(0); System.out.println("Test: " + str); } } ``` 1. `...` 代表可變的參數長度,編譯器會將其變成 **數組**,也就是說 `List<String>...list` 可以看做 `List<String>[]` 2. 一般類型可正常轉為可變長度,再轉為數組 3. Java 不允許泛型類類型的數組 (++無法確認數據類型++,會發出警告),**使用 `@SafeVarargs` 使其安全使用 (確定不會錯誤時再添加這個註解)** * `3-1`:`Object[] o = list` 這個語句會轉化為 `Object[] o = (List[]) list;` 這是正確的 但 `Arrays.asList(new Integer(123))`,就類型錯誤會拋出 `ClassCastException` 異常 * `3-2`:Arrays.asList 方法返回的是泛型數組 4. 如果沒有 `@SafeVarargs` 註解壓住錯誤的話,IDE 會警告發生 [**HashPollution**](https://hackmd.io/Dlm0Ov0URya2t6mbTgNQkQ#Heap-Pollution) 錯誤(想了結詳情情點擊連結) > ![](https://i.imgur.com/XdYW4km.png) ### Java 資源註解 * 一般用於 Java EE (Java EnterPrise),Android 較少使用 | 註解名 | 意義 | | -------------- | ---------------------------------------------------------- | | @PostConstruct | 用在控制物件生命週期的環境中,在呼叫建構函數後應立即被呼叫 | | @PreDestory | 同上,在銷毀物件之前應被呼叫的方法 | | @Resoure | 用於 Web 容器的資源植入,單一資源 | | @Resoures | 同上,表示陣列資源 | ### Java 自定義註解 * 透過 ==**@interface**== 關鍵字自定義註解,其形式與接口類似,不過多了 ==@== 符號,可當作是**標籤**,可在每個類、參數、回傳值上貼上標籤 ```java= // Sample 1 @interface HelloWorld { } @HelloWorld class MyAnnotation { } // Sample 2 in Android @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface MyAnnotation { @IdRes int id(); } @MyAnnotation(id = R.id.sample_text) TextView tv; ``` ### Java 元註解 * 可理解為 ==**++註解的註解++**,不能修飾一般方法==,可應用在其他註解上,分為五種… 1. **@Retention (註解保留期間)**(Java 1.5 加入) > 選項在 RetentionPolicy 中 (emum 類) | 元註解名 | 保留期間 | 使用 | | ------ | ----- | ----- | | RetentionPolicy.SOURCE | 註解只留在源碼中,編譯過後即捨棄 (保存到 .java 為止) | IDE、APT | | RetentionPolicy.CLASS | 註解留到編譯進行時,並**不會加載到 JVM 中** (保存到 .class 為止) | AOT 思想,插件化 | | RetentionPolicy.RUNTIME | 註解留到 runtime,**會加載到 JVM 中**,所以反射可以使用 (保存在 JVM 中) | 反射 | > 根據元註解 (Retention) 不同有不同效果 > * Source 編譯器可 **++使用註解偵錯、警告,APT++** > ![](https://i.imgur.com/KP4DBhG.png) > > ![](https://i.imgur.com/vpFt6gv.png) * Class 編譯階段,可使用 **++字節碼插入++**,但並不會保留到運行期間(也就是你可以在 `.class` 階段看到它的存在,但運行時就會消失) > ![](https://i.imgur.com/PB3qYkQ.png) > > ![](https://i.imgur.com/QVvtJxD.png) * Runtime 註解:可在 Runtime 時提取 **++反射++**,這時的註解 ==**也會代碼的一部分**==,它會保留到運行期間 > Runtime 註解通常使用在框架中 :::danger 如果是開發 Android 或其他專案時使用到 Runtime 反射機制,**要特別注意「混淆機制」,混淆會導致反射運作不正常!** 要注意去寫混肴文件,跳過混淆 ::: 2. **@Target (目標)**:**註解所修飾的對象範圍**(Java 1.5 加入) > 選項在 ElementType 中 (emum 類),`import java.lang.annotation.RetentionPolicy;` | 元素 | 適用 | | --------------------------- | ---------------------------- | | ElementType.ANNOTATION_TYPE | 註解型態宣告(給註解貼上標籤) | | ElementType.CONSTRUCTOR | 註解建構函數 | | ElementType.FIELD | 註解屬性 | | ElementType.LOCAL_VARIABLE | 註解區域變數 | | ElementType.METHOD | 註解方法 | | ElementType.PACKAGE | 註解包 | | ElementType.PARAMETER | 註解方法參數 | | ElementType.TYPE | 類別 or 介面 | | ElementType.USE | 類型的用途 | **可註解多個,使用 `{}` 包覆數值**,範例如下… ```java= @Target({ ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.TYPE} ) ``` 3. **@Documented**(Java 1.5 加入) > 將註解中的元素包含入 JavaDoc 中 4. **@Inherited (繼承)**(Java 1.5 加入) > 該註解可以被繼承 (不是說註解被繼承),如果父類使用過註解,子類沒有使用,則該子類就會繼承該父類註解 5. **@Repeatable (重複)**(Java 1.8 加入) > 該註解可多次使用 ### 自定義註解的屬性 * 在我們自定義註解時,內部 **++只可以有屬性++**,**不能有方法**! > 而註解中的 value 是一個特殊的屬性,value 不需要指定 ```java= public @interface MyAnnnotation { // value 接收 String 類型參數 String value(); // StrArray 接收 string array 類型 String[] StrArray(); // 需要指定 } ``` * 自定義註解中的屬性包含基礎的 8 個類還有 3 個特殊類,分別是… * 基礎類型:`byte`, `short`, `int`, `long`, `float`, `double`, `char`, `boolean` * 特殊類型:`Class`、`enum`、`interface` ```java= //"1. " 使用預設質、或指定 @MyAnnnotation(id=10, name="ABC", getEnum = MyEnum.THREE) class ToDo { } interface MyInterface { } enum MyEnum { ONE, TWO, THREE } @interface MyAnnnotation { int id() default -1; String name() default ""; //"2. " 不能設定方法 //void setAddress(String addr); Class<?> get() default Todo.class; MyEnum getEnum() default MyEnum.ONE; enum TestEnum { ONE, TWO, THREE } interface Test { } } ``` ## 註解提取 Android 中最常使用到的就是 APT 註解解析器,解析使用者所定義的註解 ### RUNTIME * RUNTIME 反射提取,可以猜考 [**Java 反射篇**](https://hackmd.io/cuMlqQZ4QcujENqlkA1Zrw?view#註解),像是 Gson 源碼就有使用到 (包括泛型反射) ### CLASS * 用於插件化,AOT 思想,字節碼插入程式的技術,而要插入的點可以透過註解打上標記(這裡有機會我再補充文章,其思想概念是在編譯時插入 ByteCode) ### SOURCE * **APT 是作用在 Source 過程中,Java 在編譯 java 檔案前會先儲存註解,而處理該註解的則稱為註解處理器** ```mermaid graph LR java_f(.java file) --> |執行編譯| 處理註解 subgraph 編譯 class_f(.class file) 處理註解 --> 拋棄註解 --> |產出| class_f end ``` :::info `.java` File -> 儲存註解 -> 處理註解 -> 拋棄註解 -> `.class` File ::: 1. 建立一個 MyAnnotation 的註解,並**用元注解標註 ++SOURCE++、++FIELD++** ```java= package com.oo.jnidemo; // 之後會使用到 ... @Retention(RetentionPolicy.SOURCE) // 只保存在源碼階段 @Target(ElementType.FIELD) public @interface MyAnnotation { @IdRes int id(); } ``` 2. 新建 **Java or Kotlin Library** (File > New > New Module...),並取名為 annotations > ![](https://i.imgur.com/l3Z91VK.png) > 這個 Module 作用是 ***註解處理器***,**在 processor Module 的 build.gradle 添加依賴 (implementation)** ```java= apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':annotations') } sourceCompatibility = "7" targetCompatibility = "7" ``` 在該 Library 下創建一個類,取名為 ClassProcessor,**並==繼承 AbstractProcessor==**,並覆寫以下表格的方法 (目前為了測試是在 source 所以只使用 process 函數) | 方法名稱 | 功能 | 參數功能 | | --------------------------- | ---------------------------------------------------- | --------------------------------------------- | | init | 將會被註解處理工具調用 | ProcessingEnvironment 提供工具類 | | process | 每個**處理器的主函數** | RoundEnvironment 可以查詢特定註解的被註解元素 | | getSupportedAnnotationTypes | **必須指定的方法,==指定該註解處理器是註冊給哪個註解==**,也**可以使用註解 `@SupportedAnnotationTypes`(==全類名==)** | Non | | getSupportedSourceVersion | Java 版本 | Non | ```java= //"a. " @SupportedAnnotationTypes("com.oo.jnidemo.MyAnnotation") public class MyAnnotationProcess extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //"b. " Messager messager = processingEnv.getMessager(); messager.printMessage(Diagnostic.Kind.NOTE, "!!!======== Annotation Processor ========!!!"); return false; } } ``` * a. getSupportedAnnotationTypes 返回的是一個字符串的集合,它包含本處理器將==要處理的註解類型的 **++合法全名++**==,可使用 Class 方法的 getCanonicalName * b. 輸出到 console 視窗,**因為創建時是使用 Java Lib,所以不能使用 Log,要使用 Message**,並使用 `Diagnostic.Kind.NOTE` 3. ==**註冊「註解處理器」**==,**就像是 Activity 要在 AndroidManifest;註解處理器也要註冊**,有兩種方法 **手動 & 自動**: 以下說明「Google 提供的自動註冊註解 Library」的使用,Google 開源 `AutoService` (可能版本間容不好,要小心使用,但速度較快) > 在 module processor's gradle 下新增,**annotationProcessor** ```java= dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':annotations') // new compileOnly 'com.google.auto.service:auto-service:1.0-rc4' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc2' } ``` > ![](https://i.imgur.com/GcSxDny.png) > 在註解處理器下**新增 `@AutoService(Process.class)` 的註解** ```java= // 自動註冊時,Processor.class 這是固定寫法 @AutoService(Processor.class) // 代表了該註解處理器會處理這個註解 (包名 + 全類名) @SupportedAnnotationTypes("xxx.yyy.zzz") // 指定 JDK 編譯版本 @SupportedSourceVersion(SourceVersion.RELEASE_7) // 接收的參數 @SupportedOptions({"xxx", "yyy"}) public class ClassProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { ... } } ``` 4. **使用註解**:使用的主工程項目中,**添加要使用的 project 依賴**(這個 project 就是我們在上面寫的註解處理器),之後主工程在編譯時,**指定的註解就會被分析** ```java= dependencies { ... implementation project(':annotations') // 註解 annotationProcessor project(':processor') // 註解處理器 } ``` 在主項目中使用註解 ```java= public class MainActivity extends AppCompatActivity { @MyAnnotation(id = R.id.sample_text) TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } ``` 最後 **Rebuild project,在 Build 視窗可以看到 `Task **:app:compileDebugJavaWithJavac`,Javac 在編譯 .java 文件,它在處理註解** **--結果--** > ![](https://i.imgur.com/3YsXhBc.png) ## Appendix & FAQ :::info 做 CLASS 編譯註解時出點問題,無法正常找到 AutoService > Ans : 多加入 compileOnly 'com.google.auto.service:auto-service:1.0-rc4' APT 為何必須 Clear Project or ReBuild Project? > 因為 APT 自動生成的代碼並不會刪除,clearProject 可以清除生成的 Code ::: ###### tags: `Java 基礎進階` `Android 進階`