Android自定義註解

冬榮發表於1970-01-01

按照Tatget來分的話,註解有10種型別。Target是宣告該註解使用的地方,比如使用在屬性上是field,使用在方法上是method,這裡就不一一說明了,感興趣的同學可以深入研究一下其他幾種。按照Retention來分的話,註解有3種型別,分別是SOURCE(原始碼)、CLASS(編譯)、RUNTIME(執行)。本文主要按照Retention,也就是生命週期方向來區別講解註解。原始碼註解在編譯時使用,比如@overide,生命週期短只在編譯時有作用,一般用在IDE程式碼檢測;編譯註解缺點是隻在編譯時有作用,優點是效率比執行時註解高,但是使用較複雜一些;執行時註解優點是在編譯時和執行時都能工作,但是執行時通過反射來獲取註解的值,效率較低。下面的兩個圖分別是系統定義的這2個方向類別列舉:


Android自定義註解

Android自定義註解


1.原始碼期註解(RetentionPolicy.SOURCE)

原始碼註解一般在編譯器IDE中使用,比如@overide。原始碼註解只在編譯前起作用,一般用來約束程式碼規範,在實際的業務開發中並不常見。

Android自定義註解

2.編譯期註解(RetentionPolicy.CLASS)

編譯期註解一般結合autoService+javapoet一起使用,用來在編譯期生成簡單重複的檔案,節約手寫的時間。下面我們一起來生成一個編譯期註解,注意這裡一定要新建一個java library來操作,因為核心的註解處理器父類AbstractProcessor類只在java library中有,在Android中是找不到的。假設我們需要生成的效果如下:

Android自定義註解

第一步 定義註解類

Android自定義註解

第二步 寫好註解處理器

註解處理器通過繼承AbstractProcessor的方式實現,在編譯的時候系統會尋找到所有AbstractProcessor子類執行其中的process方法,所以我們可以在process中進行生成java檔案的操作,生成以後系統會將生成的java檔案打包成class。

1.其中需要注意的是,這裡需要匯入autoservice類庫,然後註解處理器類需要通過@AutoService(Processor.class)進行標註。這是固定寫法,意思是將該類的路徑告訴註解處理器,不然系統不知道處理器路徑是沒法生成檔案的。在編譯後,這個路徑就會被儲存在javalibrary的build/META-INF下面,其中內容就是我們自定義註解處理器的完整路徑。當然如果不用@AutoService標註,直接在META-INF目錄下新建檔案也是可以的,只是更麻煩,使用@AutoService方式進行標註的使用方式如下圖:

implementation 'com.google.auto.service:auto-service:1.0-rc2'複製程式碼

Android自定義註解

Android自定義註解

2.標註好了一般需要複寫其中的4個方法,分別是init、getSupportedAnnotationTypes、getSupportedSourceVersion、process。其中process是最重要的,也是最複雜的。我們先複寫前面3個,process我們單獨拿出來寫。前面3個方法複寫如下:

Android自定義註解

其中init主要是拿到註解處理類。其中最重要的是拿到filer,這個在寫檔案的時候要用到,所以必須要複寫init拿到這個filer。elementUtils主要是用來獲取到使用了註解的類基本資訊。getSupportedAnnotationTypes是用來告訴系統編譯時支援哪些註解,在這裡我們把我們自定義的註解丟進去。getSupportedSourceVersion一般就寫SourceVersion.latestSupported就好了。再提一句,這個getSupportedAnnotationTypes和getSupportedSourceVersion方法也可以不復寫,但是要在MyAbstractor類上用註解的方式標明。

3.接下來我們來寫最重要的process方法,這個方法是整個apt最核心的部分。這裡可以完全手寫但是容易錯,速度也慢,所以一般匯入javapoet類庫,藉助javapoet來寫。我們process方法實現如下圖,在process方法中首先遍歷所有註解,找到我們自定義的MyAnotation註解。然後拿到MyAnotation物件,獲取到使用處給予的註解的值。接下來通過javapoet中的TypeSpec來拼接出需要生成的java類。最終呼叫最核心的方法JavaFile.builder(生成的路徑,拼接好的格式).build().writeTo(註解處理器的處理物件)。這樣就寫好了註解處理器的內容,接下來就坐等使用和編譯生成就好啦!這裡需要注意的是,由於註解處理器是在java library中,所以不能通過Log類來打點,這裡可以通過java的輸出方式System.out.print()的方式列印出資料來進行除錯,也可以通過從init方法中拿到Message物件來打日誌,然後在gradle console視窗進行檢視日誌資訊

compile 'com.squareup:javapoet:1.7.0'複製程式碼

Android自定義註解

Android自定義註解

提問:核心的process方法中兩個引數分別代表什麼?

第一個引數是set<TypeElement>,其中TypeElement代表類元素,也就是所有使用了指定註解的所有類,這個指定註解是指上面getSupportedAnotationTypes指定的註解,比如說本例子中在MainActivity中使用了MyAnotation,由於MyAnotation在指定的註解中,所以MainActivity這個類就會出現在該集合中。

第二個引數roundEnviroment是儲存了使用了指定註解的所有註解項資訊,不管是使用在類上還是屬性上還是方法上,都會放到該引數中。在本例子中我們獲取到所有使用了MyAnotation註解的註解項,並且取出了註解的值。

第三步 使用註解

由於註解是在javalibrary中定義的,我們首先要將該library匯入到app模組中。需要注意的是類庫宣告和註解宣告都需要做,不然是沒法跑起來的。然後我們給類中的隨意一個屬性上加上我們的註解,當然方法和類也是可以的,這裡就不一一演示了。註解使用以後我們接下來在onCreate方法裡面呼叫這個通過註解處理器生成的MySimpleAptName類中的test靜態方法來測試一下是否可以呼叫到,需要注意的是我們專案里根本沒有去手寫這個類,所以直接這樣寫報錯也很正常,一定要rebuild以後等該類生成了那麼就不會報錯了。

implementation project(':myapt')
annotationProcessor project(':myapt')複製程式碼

Android自定義註解

第四步 自動生成檔案

在執行專案或者rebuild的時候,會在使用時指定好的目錄生成class檔案,注意這裡clean的話是不能生成的。最後生成的效果如下,是不是和我們自己寫出來的類很像呢?這個時候回過頭去看MainActivity裡已經沒有報錯了,至此編譯時註解demo圓滿完成。

Android自定義註解

3.執行期註解(RetentionPolicy.RUNTIME)

執行期註解的生命週期最長,可以存活到程式執行時,也是使用最廣泛的註解型別。一般用來代替專案中重複簡單的程式碼,設定一個註解可比寫很多無用程式碼要簡潔得多。編譯器註解可以通過反射來獲取到註解類,拿到我們使用註解時設定的內容。不過由於使用反射,所以對效率會有一些影響,這也是很多開源專案(BufferKnief、Retrofit等)選擇編譯期註解的原因。不過寫起來很簡單,只需要三步就OK啦!不像編譯期註解要結合其他功能類,接下來我們一起來手寫一個執行期註解。

第一步 定義註解類

Android自定義註解

第二步 使用註解類

Android自定義註解

第三步 解析註解類

Android自定義註解

我們需要在執行的時候對註解進行解析。如上圖,我們在頁面的oncreate方法執行時首先獲取到該類所有的註解,獲取的值是一個Field[]陣列。這裡面每一個Field都代表MainActivity類中的一個註解使用項,接下來我們遍歷所有的註解項,對自己關心的項進行具體的處理。demo中我們對TextView進行了找控制元件的操作,這樣就統一處理了從而避免了專案中出現很多findViewById的操作,讓我們的程式碼變得更簡潔了。

最後:本人小萌新,如果本文有寫錯的或者描述不全的地方還望各位大佬多多指點,一起探討,共同進步!


相關文章