阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

在Android開發中,咱們爲了方便初始化Activity中的各類View,咱們可能會使用到Jake Wharton的 ButterKnife庫,這個庫是針對View、資源id等進行註解的開源庫,它可以去除掉一些醜陋不堪的樣板式代碼,使得咱們的代碼更加簡潔、易於維護,同時基於APT也使得它的效率獲得保證。 (若是你想快速瞭解ButterKnife的實現思路,能夠先閱讀 ExampleActivity$InjectAdapter類以及後續的結論,而後再回過頭來閱讀 )java

  • 下面咱們來看看 ButterKnife 的簡單使用。 首先咱們看在沒有使用ButterKnife時,咱們初始化一個Activity中的各個控件的代碼:

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

  • 在ExampleActivity函數的onCreate函數中,咱們一般會對各個子視圖進行初始化,這些代碼看起來重複性很高,並且醜陋不堪,幾乎都要對View進行強轉,當一個佈局中含有十個以上的View時,再加上爲某些View添加上事件處理等,這部分的代碼將佔用很大的篇幅。編程

  • ButterKnife就是爲了簡化這些工做而出現的,讓開發人員專一在真正有用的代碼上。使用ButterKnife以後咱們的代碼變成了這樣:

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

當運行完onCreate函數以後Activity中的幾個View就已經被初始化了。findViewById、強制轉換等樣板代碼被去除了,代碼變得更加簡單,使得咱們能夠更專一在代碼邏輯的編寫上,整個類型也更易於維護。eclipse

那麼ButterKnife的原理是什麼呢?@InjectView又是什麼?ButterKnife的inject函數又有什麼做用?ide

  • 這是由於ButterKnife使用了一種叫作編譯時註解的技術(即APT),代碼在編譯時會掃描AbstractProcessor的全部子類,而且調用這些子類的process函數,在這個函數就會將全部的代碼元素傳遞進來。函數

  • 此時咱們只須要在這個process函數中獲取全部添加了某個註解的元素,而後對這些元素進行操做,使之可以知足咱們的需求,這樣咱們就能夠在編譯期對源代碼進行處理,例如生成新的類等。在運行時,咱們經過一些接口對這些新生成的類進行調用以此完成咱們的功能。

說了這麼多仍是太抽象了,仍是以小民的例子來爲你們一一解除疑問吧。佈局

小民自從知道ButterKnife以後也被它的魅力所吸引了,因而決定研究個究竟,通過一番搜索得知ButterKnife是基於編譯時註解,而後經過APT生成輔助類,而後在運行時經過inject函數調用那些生成的輔助類來完成功能。小民決定本身寫一個只支持View 的id注入的簡版ButterKnife來深刻學習,這個庫被命名爲SimpleDagger。學習

首先小民建了一個註解類,代碼以下 : 插件

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

由於咱們的這個註解只支持View的id注入,所以它的目標元素是字段,它只存在在class文件中,由於一旦過了編譯期咱們就再也不須要它了。關於註解方面的基礎知識咱們不作過多講解,對這方面不瞭解的同窗能夠先閱讀相關書籍,例如《Java編程思想》、《Java核心技術》。設計

在添加AbstractProcessor 以前,爲了使Eclipse支持 APT 須要一些配置,能夠參考 injectdagger。Android Studio要支持 APT則須要添加APT插件,有興趣的同窗能夠自行搜索相關解決方案。對象

經過 APT 來生成輔助類型

添加這個註解以後,咱們還須要在編譯期對這個註解進行處理。上文說到,編譯器會在編譯時檢測全部的AbstractProcessor而且調用它的process函數來讓開發人員對代碼元素進行處理。所以咱們新建一個AbstractProcessor的子類,代碼以下 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

在ViewInjectorProcessor類的上面咱們看到以下註解@SupportedAnnotationTypes(「org.simple.injector.anno.*」), 這個註解代表這個類只支持org.simple.injector.anno路徑下的註解,咱們的ViewInjector註解就是在這個包下。在該類的init函數中咱們註冊了一個註解處理器,也就是ViewInjectHandler類,該類實現了AnnotationHandler接口,該接口的聲明以下 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

該接口聲明瞭兩個函數,一個是關聯ProcessingEnvironment,另外一個是handleAnnotation函數,負責處理標識了ViewInjector註解的元素。小民的設計思路是定義一個AnnotationHandler接口,每一個實現類處理一種類型的註解,例如ViewInjectHandler只處理ViewInject註解。下面咱們看看ViewInjectHandler的核心代碼 :

在handleAnnotation函數中小民獲取了全部被ViewInject註解標識了的VariableElement元素,而後將這些元素按照宿主類進行分類存到一個map中,key就是宿主類的完整類路徑,value就是這個宿主類中的全部被標識了ViewInject的VariableElement元素列表。

例如將上述ExampleActivity的示例替換成小民的SimpleDagger,使用ViewInject註解標識中三個View,代碼以下 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

  • 那麼此時ExampleActivity的完整路徑爲com.simple.apt.ExampleActivity,這個完整路徑咱們能夠經過VariableElement元素獲取到,這些VariableElement就是表明了ExampleActiivty中的title、icon、footer三個對象。
  • 所以經過ViewInjectHandler的handleAnnotation處理以後咱們的map中就含有了以com.simple.apt.ExampleActivity爲key,以title、icon、footer三個成員變量對應的VariableElement列表爲value的數據。
  • 此時執行到process函數的最後一步,這裏調用了AdapterWriter來生成輔助類,這個輔助類要生成的代碼素材就是咱們上述的VariableElement元素列表,調用的是AdapterWriter的generate函數,在AdapterWriter之下咱們還創建了一個AbsWriter來封裝一些通用邏輯,AbsWriter核心代碼以下 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

在AbsWriter的generate函數中,咱們定義了一個生成輔助類的邏輯骨架,分別爲獲取宿主類型的全部元素,而且經過第一個元素獲取宿主類所在的包以及構建輔助類的類名等,而後建立一個新的java類,最後分別寫入import、全部被註解的元素等信息寫入到輔助類當中,全部生成的輔助類都是InjectAdapter的子類。實現代碼以下的功能在DefaultJavaFileWriter類中,核心代碼以下 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

在DefaultJavaFileWriter中分別寫入了輔助類的各個部分,最終的是寫入字段的部分,也就是writeField函數。在該函數中,小民獲取了這個字段的名字,而且寫下了一行以下一行代碼

target.fieldName = ViewFinder.findViewBydId(target, ViewInject註解的值);

  • 其實這就是一個初始化某個View的語句,這個target在這個例子中就是ExampleActivity,這個ViewInject註解的值就是View的id,咱們知道每一個含有id的View最終都會在R類中生成一個整型的數值,這裏的view id就是這個整型數值。
  • 須要注意的是這些被添加註解的字段都必須是非私有的,不然不能經過target.fieldName的形式直接訪問。這些初始化代碼都被寫到了InjectAdapter子類的inject函數中,inject函數傳遞一個target參數,這個target就是元素所在的類,好比ExampleActivity,而生成的輔助類的名稱格式爲宿主類+」InjectAdapter」,例如ExampleActivityInjectAdapter,它與ExampleActivity在同一個包中,所以能夠訪問到ExampleActivity的protected、package權限的字段。

InjectAdapter 接口

InjectAdapter的聲明以下 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

ExampleActivity$InjectAdapter 類

這至關於咱們爲每一個元素都生成一行初始化代碼來替換手動在ExampleActiivty中進行findViewById,當咱們在ExampleAcivity的onCreate函數中調用SimpleDagger的inject函數時,會將ExampleActivity傳遞到InjectAdapter中,所以最後爲ExampleActivity生成的輔助類就成爲了以下這樣 :

阿里大神手把手教你Android ButterKnife 的實現思路,建議收藏!

當調用SimpleDagger的inject時就會先經過傳遞進來的類名構建一個InjectAdapter子類的類名,例如傳遞進來的是ExampleActivity,那麼此時的輔助類的類名爲 ExampleActivity$InjectAdapter,它InjectAdapter的子類。

拿到完整類名以後再反射構建一個對象,而後轉換爲InjectAdapter,最後調用inject函數。而這個生成的ExampleActivity$InjectAdapter的inject函數中又對每一個View進行了findViewBydId,也就是對它們進行了初始化。至此,這些View字段就被自動初始化了!

咱們最後再來捋一捋這個過程,大體分爲以下幾步 :

  1. 經過ViewInject註解標識一些View成員變量;
  2. 經過ViewInjecyProcessor捕獲添加了ViewInject註解的元素,而且按照宿主類進行分類;
  3. 爲每一個含有ViewInject註解的宿主類生成一個InjectAdapter輔助類,而且在它的inject函數中生成初始化View的代碼;
  4. 在SimpleDagger的inject函數中構建生成的輔助類,此時內部會它這個InjectAdapter輔助類的inject函數,這個函數中又會初始化宿主類中的View成員變量,至此,View就已經被初始化了。

SimpleDagger的完整代碼在這裏,有興趣的同窗能夠下載下來進行學習以及擴展。

須要注意的是在eclipse中使用APT須要添加JRE庫的引用,在Android Studio則須要引用APT的插件。

會持續更新哦,文章不易但願你們多多關注評論收藏!!!謝謝。

相關文章
相關標籤/搜索