在Android開發中,咱們爲了方便初始化Activity中的各類View,咱們可能會使用到Jake Wharton的 ButterKnife庫,這個庫是針對View、資源id等進行註解的開源庫,它可以去除掉一些醜陋不堪的樣板式代碼,使得咱們的代碼更加簡潔、易於維護,同時基於APT也使得它的效率獲得保證。 (若是你想快速瞭解ButterKnife的實現思路,能夠先閱讀 ExampleActivity$InjectAdapter類以及後續的結論,而後再回過頭來閱讀 )java
在ExampleActivity函數的onCreate函數中,咱們一般會對各個子視圖進行初始化,這些代碼看起來重複性很高,並且醜陋不堪,幾乎都要對View進行強轉,當一個佈局中含有十個以上的View時,再加上爲某些View添加上事件處理等,這部分的代碼將佔用很大的篇幅。編程
當運行完onCreate函數以後Activity中的幾個View就已經被初始化了。findViewById、強制轉換等樣板代碼被去除了,代碼變得更加簡單,使得咱們能夠更專一在代碼邏輯的編寫上,整個類型也更易於維護。eclipse
那麼ButterKnife的原理是什麼呢?@InjectView又是什麼?ButterKnife的inject函數又有什麼做用?ide
這是由於ButterKnife使用了一種叫作編譯時註解的技術(即APT),代碼在編譯時會掃描AbstractProcessor的全部子類,而且調用這些子類的process函數,在這個函數就會將全部的代碼元素傳遞進來。函數
說了這麼多仍是太抽象了,仍是以小民的例子來爲你們一一解除疑問吧。佈局
小民自從知道ButterKnife以後也被它的魅力所吸引了,因而決定研究個究竟,通過一番搜索得知ButterKnife是基於編譯時註解,而後經過APT生成輔助類,而後在運行時經過inject函數調用那些生成的輔助類來完成功能。小民決定本身寫一個只支持View 的id注入的簡版ButterKnife來深刻學習,這個庫被命名爲SimpleDagger。學習
首先小民建了一個註解類,代碼以下 : 插件
由於咱們的這個註解只支持View的id注入,所以它的目標元素是字段,它只存在在class文件中,由於一旦過了編譯期咱們就再也不須要它了。關於註解方面的基礎知識咱們不作過多講解,對這方面不瞭解的同窗能夠先閱讀相關書籍,例如《Java編程思想》、《Java核心技術》。設計
在添加AbstractProcessor 以前,爲了使Eclipse支持 APT 須要一些配置,能夠參考 injectdagger。Android Studio要支持 APT則須要添加APT插件,有興趣的同窗能夠自行搜索相關解決方案。對象
添加這個註解以後,咱們還須要在編譯期對這個註解進行處理。上文說到,編譯器會在編譯時檢測全部的AbstractProcessor而且調用它的process函數來讓開發人員對代碼元素進行處理。所以咱們新建一個AbstractProcessor的子類,代碼以下 :
在ViewInjectorProcessor類的上面咱們看到以下註解@SupportedAnnotationTypes(「org.simple.injector.anno.*」), 這個註解代表這個類只支持org.simple.injector.anno路徑下的註解,咱們的ViewInjector註解就是在這個包下。在該類的init函數中咱們註冊了一個註解處理器,也就是ViewInjectHandler類,該類實現了AnnotationHandler接口,該接口的聲明以下 :
該接口聲明瞭兩個函數,一個是關聯ProcessingEnvironment,另外一個是handleAnnotation函數,負責處理標識了ViewInjector註解的元素。小民的設計思路是定義一個AnnotationHandler接口,每一個實現類處理一種類型的註解,例如ViewInjectHandler只處理ViewInject註解。下面咱們看看ViewInjectHandler的核心代碼 :
在handleAnnotation函數中小民獲取了全部被ViewInject註解標識了的VariableElement元素,而後將這些元素按照宿主類進行分類存到一個map中,key就是宿主類的完整類路徑,value就是這個宿主類中的全部被標識了ViewInject的VariableElement元素列表。
例如將上述ExampleActivity的示例替換成小民的SimpleDagger,使用ViewInject註解標識中三個View,代碼以下 :
在AbsWriter的generate函數中,咱們定義了一個生成輔助類的邏輯骨架,分別爲獲取宿主類型的全部元素,而且經過第一個元素獲取宿主類所在的包以及構建輔助類的類名等,而後建立一個新的java類,最後分別寫入import、全部被註解的元素等信息寫入到輔助類當中,全部生成的輔助類都是InjectAdapter的子類。實現代碼以下的功能在DefaultJavaFileWriter類中,核心代碼以下 :
在DefaultJavaFileWriter中分別寫入了輔助類的各個部分,最終的是寫入字段的部分,也就是writeField函數。在該函數中,小民獲取了這個字段的名字,而且寫下了一行以下一行代碼
target.fieldName = ViewFinder.findViewBydId(target, ViewInject註解的值);
InjectAdapter的聲明以下 :
這至關於咱們爲每一個元素都生成一行初始化代碼來替換手動在ExampleActiivty中進行findViewById,當咱們在ExampleAcivity的onCreate函數中調用SimpleDagger的inject函數時,會將ExampleActivity傳遞到InjectAdapter中,所以最後爲ExampleActivity生成的輔助類就成爲了以下這樣 :
當調用SimpleDagger的inject時就會先經過傳遞進來的類名構建一個InjectAdapter子類的類名,例如傳遞進來的是ExampleActivity,那麼此時的輔助類的類名爲 ExampleActivity$InjectAdapter,它InjectAdapter的子類。
拿到完整類名以後再反射構建一個對象,而後轉換爲InjectAdapter,最後調用inject函數。而這個生成的ExampleActivity$InjectAdapter的inject函數中又對每一個View進行了findViewBydId,也就是對它們進行了初始化。至此,這些View字段就被自動初始化了!
咱們最後再來捋一捋這個過程,大體分爲以下幾步 :
SimpleDagger的完整代碼在這裏,有興趣的同窗能夠下載下來進行學習以及擴展。
須要注意的是在eclipse中使用APT須要添加JRE庫的引用,在Android Studio則須要引用APT的插件。
會持續更新哦,文章不易但願你們多多關注評論收藏!!!謝謝。