Android注解快速入門和實(shí)用解析

文章較長,歡迎收藏后淺斟慢酌。主要介紹和分析了 RUNTIMECLASS 下兩種注解的使用,也歡迎討論留言。


首先什么是注解?@Override就是注解,它的作用是:

  1、檢查是否正確的重寫了父類中的方法。
  2、標(biāo)明代碼,這是一個(gè)重寫的方法。

1、體現(xiàn)在于:檢查子類重寫的方法名與參數(shù)類型是否正確;檢查方法private/final/static等不能被重寫。實(shí)際上@Override對于應(yīng)用程序并沒有實(shí)際影響,從它的源碼中可以出來。
2、主要是表現(xiàn)出代碼的可讀性。

Override

作為Android開發(fā)中熟知的注解,Override只是注解的一種體現(xiàn),更多時(shí)候,注解還有以下作用:

  • 降低項(xiàng)目的耦合度。
  • 自動完成一些規(guī)律性的代碼。
  • 自動生成java代碼,減輕開發(fā)者的工作量。

一、注解基礎(chǔ)快讀

1、元注解

元注解是由java提供的基礎(chǔ)注解,負(fù)責(zé)注解其它注解,如上圖Override被@Target@Retention修飾,它們用來說明解釋其它注解,位于sdk/sources/android-25/java/lang/annotation路徑下。

元注解有:

  • @Retention:注解保留的生命周期
  • @Target:注解對象的作用范圍。
  • @Inherited:@Inherited標(biāo)明所修飾的注解,在所作用的類上,是否可以被繼承。
  • @Documented:如其名,javadoc的工具文檔化,一般不關(guān)心。
@Retention

Retention說標(biāo)明了注解被生命周期,對應(yīng)RetentionPolicy的枚舉,表示注解在何時(shí)生效:

  • SOURCE:只在源碼中有效,編譯時(shí)拋棄,如上面的@Override

  • CLASS:編譯class文件時(shí)生效。

  • RUNTIME:運(yùn)行時(shí)才生效。

如下圖X1com.android.support:support-annotations中的Nullable注解,會在編譯期判斷,被注解的參數(shù)是否會空,具體后續(xù)分析。

圖X1

@Target

Target標(biāo)明了注解的適用范圍,對應(yīng)ElementType枚舉,明確了注解的有效范圍。

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

如上圖X1所示,@Nullable可用于注解方法,參數(shù),類成員,注解,包聲明中,常用例子如下所示:

 /**
   * Nullable表明
   * bind方法的參數(shù)target和返回值Data可以為null
   */
  @Nullable 
  public static Data bind(@Nullable Context target) {
    //do someThing and return
    return bindXXX(target);
  }
@Inherited

注解所作用的類,在繼承時(shí)默認(rèn)無法繼承父類的注解。除非注解聲明了 @Inherited。同時(shí)Inherited聲明出來的注,只對類有效,對方法/屬性無效。

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

  • 類Child繼承了父類Parent的@AInherited,不繼承@BNotInherited
  • 重寫的方法testOverride()不繼承Parent的任何注解;
  • testNotOverride()因?yàn)闆]有被重寫,所以注解依然生效。
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
public @interface AInherited {  
    String value();  
}  
@Retention(RetentionPolicy.RUNTIME)  
public @interface BNotInherited {  
    String value();  
}  

@AInherited("Inherited")  
@BNotInherited("沒Inherited")  
public class Parent {  

    @AInherited("Inherited")  
    @BNotInherited("沒Inherited")  
    public void testOverride(){  
          
    }  
    @AInherited("Inherited")  
    @BNotInherited("沒Inherited")  
    public void testNotOverride(){
    }
}  

/**
  * Child繼承了Parent的AInherited注解
  * BNotInherited因?yàn)闆]有@Inherited聲明,不能被繼承
  */
public class Child extends Parent {  
  
  /**
   * 重寫的testOverride不繼承任何注解
   * 因?yàn)镮nherited不作用在方法上
   */
    @Override  
    public void testOverride() {  
    }  

  /**
   * testNotOverride沒有被重寫
   * 所以注解AInherited和BNotInherited依然生效。
   */
}  

2、自定義注解

2.1 運(yùn)行時(shí)注解

了解了元注解后,看看如何實(shí)現(xiàn)和使用自定義注解。這里我們簡單介紹下運(yùn)行時(shí)注解RUNTIME,編譯時(shí)注解CLASS留著后面分析。

首先,創(chuàng)建一個(gè)注解遵循: public @interface 注解名 {方法參數(shù)},如下方@getViewTo注解:

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

然后如下方所示,我們將注解描述在Activity的成員變量mTvmBtn中,在App運(yùn)行時(shí),通過反射將findViewbyId得到的控件,注入到mTvmBtn中。

是不是很熟悉,有點(diǎn)ButterKnife的味道?當(dāng)然,ButterKnife比這個(gè)高級多,畢竟反射多了影響效率,不過我們明白了,可以通過注解來注入和創(chuàng)建對象,這樣可以在一定程度節(jié)省代碼量。

public class MainActivity extends AppCompatActivity {

    @getViewTo(R.id.textview)
    private TextView mTv;

    @getViewTo(R.id.button)
    private Button mBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //通過注解生成View;
        getAllAnnotationView();
    }

    /**
     * 解析注解,獲取控件
     */
    private void getAllAnnotationView() {
        //獲得成員變量
        Field[] fields = this.getClass().getDeclaredFields();
    
        for (Field field : fields) {
          try {
            //判斷注解
            if (field.getAnnotations() != null) {
              //確定注解類型
              if (field.isAnnotationPresent(GetViewTo.class)) {
                //允許修改反射屬性
                field.setAccessible(true);
                GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);
                //findViewById將注解的id,找到View注入成員變量中
                field.set(this, findViewById(getViewTo.value()));
              }
            }
          } catch (Exception e) {
          }
        }
      }
  

}
2.2 編譯時(shí)注解

運(yùn)行時(shí)注解RUNTIME如上2.1所示,大多數(shù)時(shí)候?qū)嵲谶\(yùn)行時(shí)使用反射來實(shí)現(xiàn)所需效果,這很大程度上影響效率,如果BufferKnife的每個(gè)View注入不可能如何實(shí)現(xiàn)。實(shí)際上,ButterKnife使用的是編譯時(shí)注解CLASS,如下圖X2.2,是ButterKnife的@BindView注解,它是一個(gè)編譯時(shí)注解,在編譯時(shí)生成對應(yīng)java代碼,實(shí)現(xiàn)注入

圖X2.2

說到編譯時(shí)注解,就不得不說注解處理器*** AbstractProcessor,如果你有注意,一般第三方注解相關(guān)的類庫,如bufferKnike、ARouter,都有一個(gè)Compiler命名的Module,如下圖X2.3*,這里面一般都是注解處理器,用于編譯時(shí)處理對應(yīng)的注解。

注解處理器(Annotation Processor)是javac的一個(gè)工具,它用來在編譯時(shí)掃描和處理注解(Annotation)。你可以對自定義注解,并注冊相應(yīng)的注解處理器,用于處理你的注解邏輯。

圖X2.3

如下所示,實(shí)現(xiàn)一個(gè)自定義注解處理器,至少重寫四個(gè)方法,并且注冊你的自定義Processor,詳細(xì)可參考下方代碼CustomProcessor

  • @AutoService(Processor.class),谷歌提供的自動注冊注解,為你生成注冊Processor所需要的格式文件(com.google.auto相關(guān)包)。

  • init(ProcessingEnvironment env),初始化處理器,一般在這里獲取我們需要的工具類。

  • getSupportedAnnotationTypes(),指定注解處理器是注冊給哪個(gè)注解的,返回指定支持的注解類集合。

  • getSupportedSourceVersion() ,指定java版本。

  • process(),處理器實(shí)際處理邏輯入口。

@AutoService(Processor.class)
public class CustomProcessor extends AbstractProcessor {

    /**
     * 注解處理器的初始化
     * 一般在這里獲取我們需要的工具類
     * @param processingEnvironment 提供工具類Elements, Types和Filer
     */
    @Override
    public synchronized void init(ProcessingEnvironment env){ 
        super.init(env);
        //Element代表程序的元素,例如包、類、方法。
        mElementUtils = env.getElementUtils();

        //處理TypeMirror的工具類,用于取類信息
        mTypeUtils = env.getTypeUtils();

         //Filer可以創(chuàng)建文件
        mFiler = env.getFiler();

        //錯(cuò)誤處理工具
        mMessages = env.getMessager();
    }

    /**
     * 處理器實(shí)際處理邏輯入口
     * @param set
     * @param roundEnvironment 所有注解的集合
     * @return 
     */
    @Override
    public boolean process(Set<? extends TypeElement> annoations, 
      RoundEnvironment env) {
        //do someThing
    }

    //指定注解處理器是注冊給哪個(gè)注解的,返回指定支持的注解類集合。
    @Override
    public Set<String> getSupportedAnnotationTypes() { 
          Set<String> sets = new LinkedHashSet<String>();
          
          //大部分class而已getName、getCanonicalNam這兩個(gè)方法沒有什么不同的。
          //但是對于array或內(nèi)部類等就不一樣了。
          //getName返回的是[[Ljava.lang.String之類的表現(xiàn)形式,
          //getCanonicalName返回的就是跟我們聲明類似的形式。
          sets(BindView.class.getCanonicalName());

          return sets;
    }

    //指定Java版本,一般返回最新版本即可
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

}

首先,我們梳理下一般處理器處理邏輯:

  • 1、遍歷得到源碼中,需要解析的元素列表。
  • 2、判斷元素是否可見和符合要求。
  • 3、組織數(shù)據(jù)結(jié)構(gòu)得到輸出類參數(shù)。
  • 4、輸入生成java文件。
  • 5、錯(cuò)誤處理。

然后,讓我們理解一個(gè)概念:Element,因?yàn)樗俏覀儷@取注解的基礎(chǔ)。

Processor處理過程中,會掃描全部Java源碼,代碼的每一個(gè)部分都是一個(gè)特定類型的Element,它們像是XML一層的層級機(jī)構(gòu),比如類、變量、方法等,每個(gè)Element代表一個(gè)靜態(tài)的、語言級別的構(gòu)件,如下方代碼所示。

package android.demo; // PackageElement

// TypeElement
public class DemoClass {

    // VariableElement
    private boolean mVariableType;

    // VariableElement
    private VariableClassE m VariableClassE;

    // ExecuteableElement
    public DemoClass () {
    }

    // ExecuteableElement
    public void resolveData (Demo data   //TypeElement ) {
    }
}

其中,Element代表的是源代碼,而TypeElement代表的是源代碼中的類型元素,例如類。然而,TypeElement并不包含類本身的信息。你可以從TypeElement中獲取類的名字,但是你獲取不到類的信息,例如它的父類。這種信息需要通過TypeMirror獲取。你可以通過調(diào)用elements.asType()獲取元素的TypeMirror

1、知道了Element,我們就可以通過process 中的RoundEnvironment去獲取,掃描到的所有元素,如下圖X2.4,通過env.getElementsAnnotatedWith,我們可以獲取被@BindView注解的元素的列表,其中validateElement校驗(yàn)元素是否可用。

**圖X2.4**

2、因?yàn)?code>env.getElementsAnnotatedWith返回的,是所有被注解了@ BindView的元素的列表。所以有時(shí)候我們還需要走一些額外的判斷,比如,檢查這些Element是否是一個(gè)類:

  @Override
  public boolean process(Set<? extends TypeElement> an, RoundEnvironment env) {
    for (Element e : env.getElementsAnnotatedWith(BindView.class)) {
      // 檢查元素是否是一個(gè)類
      if (ae.getKind() != ElementKind.CLASS) {
            ...
      }
   }
   ...
}

3、javapoet (com.squareup:javapoet)是一個(gè)根據(jù)指定參數(shù),生成java文件的開源庫,有興趣了解javapoet的可以看下javapoet——讓你從重復(fù)無聊的代碼中解放出來,在處理器中,按照參數(shù)創(chuàng)建出 JavaFile之后,通Filer利用javaFile.writeTo(filer);就可以生成你需要的java文件。

4、錯(cuò)誤處理,在處理器中,我們不能直接拋出一個(gè)異常,因?yàn)樵趐rocess()中拋出一個(gè)異常,會導(dǎo)致運(yùn)行注解處理器的JVM崩潰,導(dǎo)致跟蹤棧信息十分混亂。因此,注解處理器就有一個(gè)Messager類,一般通過messager.printMessage( Diagnostic.Kind.ERROR, StringMessage, element)即可正常輸出錯(cuò)誤信息。

至此,你的注解處理器完成了所有的邏輯。可以看出,編譯時(shí)注解實(shí)在編譯時(shí)生成java文件,然后將生產(chǎn)的java文件注入到源碼中,在運(yùn)行時(shí)并不會像運(yùn)行時(shí)注解一樣,影響效率和資源。

總結(jié)

我們就利用ButterKnife的流程,簡單舉例做個(gè)總結(jié)吧。

  • 1、@BindView在編譯時(shí),根據(jù)Acitvity生產(chǎn)了XXXActivity$$ViewBinder.java。
  • 2、Activity中調(diào)用的ButterKnife.bind(this);,通過this的類名字,加$$ViewBinder,反射得到了ViewBinder,和編譯處理器生產(chǎn)的java文件關(guān)聯(lián)起來了,并將其存在map中緩存,然后調(diào)用ViewBinder.bind()
  • 3、在ViewBinder的bind方法中,通過id,利用ButterKnife的butterknife.internal.Utils工具類中的封裝方法,將findViewById()控件注入到Activity的參數(shù)中。

好了,通過上面的流程,是不是把編譯時(shí)注解的生成和使用連接起來了呢?有問題還請各位留言談?wù)摗?/p>

參考資料

這次的文章很嚴(yán)肅有木有
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 6,745評論 4 31
  • 什么是注解注解分類注解作用分類 元注解 Java內(nèi)置注解 自定義注解自定義注解實(shí)現(xiàn)及使用編譯時(shí)注解注解處理器注解處...
    Mr槑閱讀 1,099評論 0 3
  • 現(xiàn)在市面上很多框架都有使用到注解,比如butterknife庫、EventBus庫、Retrofit庫等等。...
    tuacy閱讀 5,662評論 1 15
  • 一般的,注解在 Android 中有兩種應(yīng)用方式,一種方式是基于反射的,即在程序的運(yùn)行期間獲取類信息進(jìn)行反射調(diào)用;...
    開發(fā)者如是說閱讀 677評論 0 6
  • 0.導(dǎo)語 Java 作為一門低語法糖的語言,核心在其虛擬機(jī)的實(shí)現(xiàn),語言層面提供的“黑科技”并不多,而注解就是其中比...
    AylmerChen閱讀 5,357評論 3 12