Android註解快速入門和實用解析

本文講的是Android註解快速入門和實用解析,首先什麼是註解?@Override就是註解,它的做用是:java

  • 檢查是否正確的重寫了父類中的方法。
  • 標明代碼,這是一個重寫的方法。

一、體如今於:檢查子類重寫的方法名與參數類型是否正確;檢查方法private/final/static等不能被重寫。實際上@Override對於應用程序並無實際影響,從它的源碼中能夠出來。android

Android註解快速入門和實用解析

二、主要是表現出代碼的可讀性。緩存

Android註解快速入門和實用解析

做爲Android開發中熟知的註解,Override只是註解的一種體現,更多時候,註解還有如下做用:數據結構

  • 下降項目的耦合度。
  • 自動完成一些規律性的代碼。
  • 自動生成java代碼,減輕開發者的工做量。

1、註解基礎快讀ide

一、元註解工具

元註解是由java提供的基礎註解,負責註解其它註解,如上圖Override被@Target和@Retention修飾,它們用來講明解釋其它註解,位於sdk/sources/android-25/java/lang/annotation路徑下。this

元註解有:google

  • @Retention:註解保留的生命週期
  • @Target:註解對象的做用範圍。
  • @Inherited:@Inherited標明所修飾的註解,在所做用的類上,是否能夠被繼承。
  • @Documented:如其名,javadoc的工具文檔化,通常不關心。

@Retention3d

Retention說標明瞭註解被生命週期,對應RetentionPolicy的枚舉,表示註解在什麼時候生效:對象

  • SOURCE:只在源碼中有效,編譯時拋棄,如上面的@Override。
  • CLASS:編譯class文件時生效。
  • RUNTIME:運行時才生效。

以下圖X1,com.android.support:support-annotations中的Nullable註解,會在編譯期判斷,被註解的參數是否會空,具體後續分析。

Android註解快速入門和實用解析

@Target

Target標明瞭註解的適用範圍,對應ElementType枚舉,明確了註解的有效範圍。

  • TYPE:類、接口、枚舉、註解類型。
  • FIELD:類成員(構造方法、方法、成員變量)。
  • METHOD:方法。
  • PARAMETER:參數。
  • CONSTRUCTOR:構造器。
  • LOCAL_VARIABLE:局部變量。
  • ANNOTATION_TYPE:註解。
  • PACKAGE:包聲明。
  • TYPE_PARAMETER:類型參數。
  • TYPE_USE:類型使用聲明。

如上圖X1所示,@Nullable可用於註解方法,參數,類成員,註解,包聲明中,經常使用例子以下所示:

 
  1. /** 
  2.   * Nullable代表 
  3.   * bind方法的參數target和返回值Data能夠爲null 
  4.   */ 
  5.  @Nullable  
  6.  public static Data bind(@Nullable Context target) { 
  7.    //do someThing and return 
  8.    return bindXXX(target); 
  9.  } 

@Inherited

註解所做用的類,在繼承時默認沒法繼承父類的註解。除非註解聲明瞭 @Inherited。同時Inherited聲明出來的注,只對類有效,對方法/屬性無效。

以下方代碼,註解類@AInherited聲明瞭Inherited ,而註解BNotInherited 沒有,所在在它們的修飾下:

  • 類Child繼承了父類Parent的@AInherited,不繼承@BNotInherited;
  • 重寫的方法testOverride()不繼承Parent的任何註解;
  • testNotOverride()由於沒有被重寫,因此註解依然生效。
 
  1. @Retention(RetentionPolicy.RUNTIME)   
  2. @Inherited   
  3. public @interface AInherited {   
  4.     String value();   
  5. }   
  6. @Retention(RetentionPolicy.RUNTIME)   
  7. public @interface BNotInherited {   
  8.     String value();   
  9. }   
  10.  
  11. @AInherited("Inherited")   
  12. @BNotInherited("沒Inherited")   
  13. public class Parent {   
  14.  
  15.     @AInherited("Inherited")   
  16.     @BNotInherited("沒Inherited")   
  17.     public void testOverride(){   
  18.  
  19.     }   
  20.     @AInherited("Inherited")   
  21.     @BNotInherited("沒Inherited")   
  22.     public void testNotOverride(){ 
  23.     } 
  24. }   
  25.  
  26. /** 
  27.   * Child繼承了Parent的AInherited註解 
  28.   * BNotInherited由於沒有@Inherited聲明,不能被繼承 
  29.   */ 
  30. public class Child extends Parent {   
  31.  
  32.   /** 
  33.    * 重寫的testOverride不繼承任何註解 
  34.    * 由於Inherited不做用在方法上 
  35.    */ 
  36.     @Override   
  37.     public void testOverride() {   
  38.     }   
  39.  
  40.   /** 
  41.    * testNotOverride沒有被重寫 
  42.    * 因此註解AInherited和BNotInherited依然生效。 
  43.    */ 

二、自定義註解

2.1 運行時註解

瞭解了元註解後,看看如何實現和使用自定義註解。這裏咱們簡單介紹下運行時註解RUNTIME,編譯時註解CLASS留着後面分析。

首先,建立一個註解遵循: public @interface 註解名 {方法參數},以下方@getViewTo註解:

 
  1. @Target({ElementType.FIELD}) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface getViewTo { 
  4.     int value() default  -1; 

而後以下方所示,咱們將註解描述在Activity的成員變量mTv和mBtn中,在App運行時,經過反射將findViewbyId獲得的控件,注入到mTv和mBtn中。

是否是很熟悉,有點ButterKnife的味道?固然,ButterKnife比這個高級多,畢竟反射多了影響效率,不過咱們明白了,能夠經過註解來注入和建立對象,這樣能夠在必定程度節省代碼量。

 
  1. public class MainActivity extends AppCompatActivity { 
  2.  
  3.     @getViewTo(R.id.textview) 
  4.     private TextView mTv; 
  5.  
  6.     @getViewTo(R.id.button) 
  7.     private Button mBtn; 
  8.  
  9.     @Override 
  10.     protected void onCreate(Bundle savedInstanceState) { 
  11.         super.onCreate(savedInstanceState); 
  12.         setContentView(R.layout.activity_main); 
  13.  
  14.         //經過註解生成View; 
  15.         getAllAnnotationView(); 
  16.     } 
  17.  
  18.     /** 
  19.      * 解析註解,獲取控件 
  20.      */ 
  21.     private void getAllAnnotationView() { 
  22.         //得到成員變量 
  23.         Field[] fields = this.getClass().getDeclaredFields(); 
  24.  
  25.         for (Field field : fields) { 
  26.           try { 
  27.             //判斷註解 
  28.             if (field.getAnnotations() != null) { 
  29.               //肯定註解類型 
  30.               if (field.isAnnotationPresent(GetViewTo.class)) { 
  31.                 //容許修改反射屬性 
  32.                 field.setAccessible(true); 
  33.                 GetViewTo getViewTo = field.getAnnotation(GetViewTo.class); 
  34.                 //findViewById將註解的id,找到View注入成員變量中 
  35.                 field.set(this, findViewById(getViewTo.value())); 
  36.               } 
  37.             } 
  38.           } catch (Exception e) { 
  39.           } 
  40.         } 
  41.       } 
  42.  

2.2 編譯時註解

運行時註解RUNTIME如上2.1所示,大多數時候實在運行時使用反射來實現所需效果,這很大程度上影響效率,若是BufferKnife的每一個View注入不可能如何實現。實際上,ButterKnife使用的是編譯時註解CLASS,以下圖X2.2,是ButterKnife的@BindView註解,它是一個編譯時註解,在編譯時生成對應java代碼,實現注入。

Android註解快速入門和實用解析

說到編譯時註解,就不得不說註解處理器 AbstractProcessor,若是你有注意,通常第三方註解相關的類庫,如bufferKnike、ARouter,都有一個Compiler命名的Module,以下圖X2.3,這裏面通常都是註解處理器,用於編譯時處理對應的註解。

註解處理器(Annotation Processor)是javac的一個工具,它用來在編譯時掃描和處理註解(Annotation)。你能夠對自定義註解,並註冊相應的註解處理器,用於處理你的註解邏輯。

Android註解快速入門和實用解析

以下所示,實現一個自定義註解處理器,至少重寫四個方法,而且註冊你的自定義Processor,詳細可參考下方代碼CustomProcessor。

  • @AutoService(Processor.class),谷歌提供的自動註冊註解,爲你生成註冊Processor所須要的格式文件(com.google.auto相關包)。
  • init(ProcessingEnvironment env),初始化處理器,通常在這裏獲取咱們須要的工具類。
  • getSupportedAnnotationTypes(),指定註解處理器是註冊給哪一個註解的,返回指定支持的註解類集合。
  • getSupportedSourceVersion() ,指定java版本。
  • process(),處理器實際處理邏輯入口。
 
  1. @AutoService(Processor.class) 
  2. public class CustomProcessor extends AbstractProcessor { 
  3.  
  4.     /** 
  5.      * 註解處理器的初始化 
  6.      * 通常在這裏獲取咱們須要的工具類 
  7.      * @param processingEnvironment 提供工具類Elements, Types和Filer 
  8.      */ 
  9.     @Override 
  10.     public synchronized void init(ProcessingEnvironment env){  
  11.         super.init(env); 
  12.         //Element表明程序的元素,例如包、類、方法。 
  13.         mElementUtils = env.getElementUtils(); 
  14.  
  15.         //處理TypeMirror的工具類,用於取類信息 
  16.         mTypeUtils = env.getTypeUtils(); 
  17.  
  18.          //Filer能夠建立文件 
  19.         mFiler = env.getFiler(); 
  20.  
  21.         //錯誤處理工具 
  22.         mMessages = env.getMessager(); 
  23.     } 
  24.  
  25.     /** 
  26.      * 處理器實際處理邏輯入口 
  27.      * @param set 
  28.      * @param roundEnvironment 全部註解的集合 
  29.      * @return  
  30.      */ 
  31.     @Override 
  32.     public boolean process(Set<? extends TypeElement> annoations,  
  33.       RoundEnvironment env) { 
  34.         //do someThing 
  35.     } 
  36.  
  37.     //指定註解處理器是註冊給哪一個註解的,返回指定支持的註解類集合。 
  38.     @Override 
  39.     public Set<String> getSupportedAnnotationTypes() {  
  40.           Set<String> sets = new LinkedHashSet<String>(); 
  41.  
  42.           //大部分class而已getName、getCanonicalNam這兩個方法沒有什麼不一樣的。 
  43.           //可是對於array或內部類等就不同了。 
  44.           //getName返回的是[[Ljava.lang.String之類的表現形式, 
  45.           //getCanonicalName返回的就是跟咱們聲明相似的形式。 
  46.           sets(BindView.class.getCanonicalName()); 
  47.  
  48.           return sets; 
  49.     } 
  50.  
  51.     //指定Java版本,通常返回最新版本便可 
  52.     @Override 
  53.     public SourceVersion getSupportedSourceVersion() { 
  54.         return SourceVersion.latestSupported(); 
  55.     } 
  56.  

首先,咱們梳理下通常處理器處理邏輯:

  1. 遍歷獲得源碼中,須要解析的元素列表。
  2. 判斷元素是否可見和符合要求。
  3. 組織數據結構獲得輸出類參數。
  4. 輸入生成java文件。
  5. 錯誤處理。

而後,讓咱們理解一個概念:Element,由於它是咱們獲取註解的基礎。

Processor處理過程當中,會掃描所有Java源碼,代碼的每個部分都是一個特定類型的Element,它們像是XML一層的層級機構,好比類、變量、方法等,每一個Element表明一個靜態的、語言級別的構件,以下方代碼所示。

 
  1. package android.demo; // PackageElement  
  2. // TypeElement 
  3. public class DemoClass {  
  4.     // VariableElement 
  5.     private boolean mVariableType;  
  6.     // VariableElement 
  7.     private VariableClassE m VariableClassE;  
  8.     // ExecuteableElement 
  9.     public DemoClass () { 
  10.     }  
  11.     // ExecuteableElement 
  12.     public void resolveData (Demo data   //TypeElement ) { 
  13.     } 

其中,Element表明的是源代碼,而TypeElement表明的是源代碼中的類型元素,例如類。然而,TypeElement並不包含類自己的信息。你能夠從TypeElement中獲取類的名字,可是你獲取不到類的信息,例如它的父類。這種信息須要經過TypeMirror獲取。你能夠經過調用elements.asType()獲取元素的TypeMirror。

一、知道了Element,咱們就能夠經過process 中的RoundEnvironment去獲取,掃描到的全部元素,以下圖X2.4,經過env.getElementsAnnotatedWith,咱們能夠獲取被@BindView註解的元素的列表,其中validateElement校驗元素是否可用。

Android註解快速入門和實用解析

二、由於env.getElementsAnnotatedWith返回的,是全部被註解了@ BindView的元素的列表。因此有時候咱們還須要走一些額外的判斷,好比,檢查這些Element是不是一個類:

 
  1. @Override 
  2.   public boolean process(Set<? extends TypeElement> an, RoundEnvironment env) { 
  3.     for (Element e : env.getElementsAnnotatedWith(BindView.class)) { 
  4.       // 檢查元素是不是一個類 
  5.       if (ae.getKind() != ElementKind.CLASS) { 
  6.             ... 
  7.       } 
  8.    } 
  9.    ... 

三、javapoet (com.squareup:javapoet)是一個根據指定參數,生成java文件的開源庫,有興趣瞭解javapoet的能夠看下javapoet——讓你從重複無聊的代碼中解放出來,在處理器中,按照參數建立出 JavaFile以後,通Filer利用javaFile.writeTo(filer);就能夠生成你須要的java文件。

四、錯誤處理,在處理器中,咱們不能直接拋出一個異常,由於在process()中拋出一個異常,會致使運行註解處理器的JVM崩潰,致使跟蹤棧信息十分混亂。所以,註解處理器就有一個Messager類,通常經過messager.printMessage( Diagnostic.Kind.ERROR, StringMessage, element)便可正常輸出錯誤信息。

至此,你的註解處理器完成了全部的邏輯。能夠看出,編譯時註解實在編譯時生成java文件,而後將生產的java文件注入到源碼中,在運行時並不會像運行時註解同樣,影響效率和資源。

總結

咱們就利用ButterKnife的流程,簡單舉例作個總結吧。

  1. @BindView在編譯時,根據Acitvity生產了XXXActivity$$ViewBinder.java。
  2. Activity中調用的ButterKnife.bind(this);,經過this的類名字,加$$ViewBinder,反射獲得了ViewBinder,和編譯處理器生產的java文件關聯起來了,並將其存在map中緩存,而後調用ViewBinder.bind()。
  3. 在ViewBinder的bind方法中,經過id,利用ButterKnife的butterknife.internal.Utils工具類中的封裝方法,將findViewById()控件注入到Activity的參數中。

好了,經過上面的流程,是否是把編譯時註解的生成和使用鏈接起來了呢?有問題還請各位留言談論。


閱讀原文

相關文章
相關標籤/搜索