Android -- Annotation(注解)原理詳解及常見框架應用


1,我們在上一篇講到了EventBus源碼及3.0版本的簡單使用,知道了我們3.0版本是使用注解方式標記事件響應方法的,這里我們就有一個疑問了,為什么在一個方法加上類似於“@Subscribe”,就可以讓我們的反射找到我們的事件響應方法。而且使用過BufferKnife、Dagger、Retrofit的同學或常見“@XXX”這種關鍵字 。so,抱着弄懂一切不明真相的精神,我們開始了這篇文章的探索。

2,什么是注解?

  它提供了一種安全的類似注釋的機制,用來將任何的信息或元數據(metadata)與程序元素(類、方法、成員變量等)進行關聯。為程序的元素(類、方法、成員變量)加上更直觀更明了的說明,這些說明信息是與程序的業務邏輯無關,並且供指定的工具或框架使用。

  ok,知道大家對上面的注解定義現在是一臉懵逼,所以舉一個創建的栗子吧,“@Override”關鍵字我們熟悉吧,我們創建Activity的時候,重寫onCreate方法,方法上面就有這個標記,拿我們看一下它的源碼是什么

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

  "Target"、"Rentention" 這些什么是一些什么啊 ,感覺懵逼加重。如果我們自己寫一個方法,然后在上面加上一個"@Override",看一下效果

  看到沒,直接就報錯了,所以我們在想,到底什么時候才能使用“@Override”關鍵字?能不能使用類似於“A”、“B”關鍵字?擁有這些標記的方法、屬性、類有什么用啊?所以帶着這些問題我們先來了解簡單的常理知識。

  • 元注解

  看到這個名字可能同學們都有點蒙了,學過Python的同學肯定知道元數據。但是這里和我們的元數據沒有一毛錢關系,定義也很簡單,"Java中提供了四個元注解,專門注解其它注解。" 

   @Documented –注解是否將包含在JavaDoc中
   @Retention –什么時候使用該注解
   @Target –注解用於什么地方
   @Inherited – 是否允許子類繼承該注解

  咦,看着上面的Retention 、Target  這不是我們"@Override"關鍵字中見過嗎,哦,現在知道了它們對應的是什么意思了,讓我們具體的看一下四個元注解的詳細信息吧

  • @Documented
是否會保存到 Javadoc 文檔中

  這個很簡單,我們看看EventBus3.0的@Subscribe的源碼

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default 0;
}

  果然,這里使用了我們的@Documented元注解了,我們繼續往下看

  • @Inherited
是否可以被繼承,默認為 false

  我們這時候有個疑問,當我們的這個字段值為true的時候,並修飾的我們的class類表示的什么。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。

  • @Target 

  這個注解表示注解的作用范圍,主要有如下:

ElementType.FIELD 注解作用於變量

ElementType.METHOD 注解作用於方法

ElementType.PARAMETER 注解作用於參數

ElementType.CONSTRUCTOR 注解作用於構造方法

ElementType.LOCAL_VARIABLE 注解作用於局部變量

ElementType.PACKAGE 注解作用於包

  而我們常見的基本上適用於變量、方法,這里給大家舉例一下Retrofit2.0中的"@Query"注解,這是使用的就是PARAMETER 作用於參數,源碼如下:

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {
  /** The query parameter name. */
  String value();

  /**
   * Specifies whether the parameter {@linkplain #value() name} and value are already URL encoded.
   */
  boolean encoded() default false;
}
  • @Retention 

  這個表示注解的保留方式,具體有一下三種類型

  RetentionPolicy.SOURCE : 只保留在源碼中,不保留在class中,同時也不加載到虛擬機中 。在編譯階段丟棄。這些注解在編譯結束之后就不再有任何意義,所以它們不會寫入字節碼。
  RetentionPolicy.CLASS : 保留在源碼中,同時也保留到class中,但是不加載到虛擬機中 。在類加載的時候丟棄。在字節碼文件的處理中有用。注解默認使用這種方式
  RetentionPolicy.RUNTIME : 保留到源碼中,同時也保留到class中,最后加載到虛擬機中。始終不會丟棄,運行期也保留該注解,因此可以使用反射機制讀取該注解的信息。我們自定義的注解通常使用這種方式。

  ok,把以上四種元數據的概念都弄懂了之后,我們在看我們之前看的"@Override"源碼

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

  表示該注解作用於方法,且只保存在源碼中。

  ok,再看一下我們Java中常見的注解有哪些吧,這里只用了解了解

@Override: 表示該方法是重寫父類中的方法,編譯的時候會檢查該方法,如果這個方法不是父類中存在的將會報錯
@Deprecated: 表示該方法時已經過時的,表示該方法有風險或者有更好的替代方法
@SuppressWarnings: 表示在編譯的時候忽略某種錯誤,如版本檢查等

  這里還有一個疑問,我們的程序是怎么知道某個類中包含注解方法的呢?又是在獲取注解方法中的相應屬性的呢?

  這里我們使用反射來拿到class中的注解方法,主要使用這句關鍵代碼

method.getAnnotation(XXXAnnonation.class)

3,自定義注解

  我們上面了解了一系列注解知識,現在我們想自定義自己的注解,作用於方法,注解里面的屬性有name和id,所以我們代碼如下:

package com.qianmo.eventbustest;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by wangjitao on 2017/4/14 0014.
 * E-Mail:543441727@qq.com
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnonation {
    String name() default "";

    int id() default 0;
}

  然后在Activity中標記我們自己編寫的方法,設置annonation的屬性

  @TestAnnonation(name = "wangjitao", id = 1)
    public void testMothed() {
        tv_message.setText(name + ":" + id);
    }

  提供反射的方法拿到Annonation中對應的屬性,保存數據

 private void getAnnonationData(Class clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            TestAnnonation testAnnonation = method.getAnnotation(TestAnnonation.class);
            if (testAnnonation != null) {
                name = testAnnonation.name();
                id = testAnnonation.id();
            }
        }
    }

  完整代碼如下:

public class MainActivity extends AppCompatActivity {
    private Button btn_skip;
    private TextView tv_message;
    private String name;
    private int id;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_skip = (Button) findViewById(R.id.btn_skip);
        tv_message = (TextView) findViewById(R.id.tv_message);
        btn_skip.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                testMothed();
            }
        });

        getAnnonationData(MainActivity.class);
    }

    @TestAnnonation(name = "wangjitao", id = 1)
    public void testMothed() {
        tv_message.setText(name + ":" + id);
    }

    private void getAnnonationData(Class clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            TestAnnonation testAnnonation = method.getAnnotation(TestAnnonation.class);
            if (testAnnonation != null) {
                name = testAnnonation.name();
                id = testAnnonation.id();
            }
        }
    }

}

  效果如下:

  ok,今天就給大家普及到這里,搞懂了以上的注解知識之后,我們看Retrofit2.0的 @GET、@POST等還有難度嗎?  小伙子們趕緊去看看來弄懂之前沒搞懂的注解原理吧。。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM