博主在初學註解的時候看到網上的介紹大部分都是直接介紹用法或者功能,沒有實際的應用場景,篇幅又很長致使學習的時候難以理解其意圖,並且學完就忘QAQ。本篇文章中我將結合實際的應用場景儘量由淺入深,平緩的介紹java註解。java
咱們能夠看到,註解的做用有三方面:git
編寫doc文檔:這個就咱們很經常使用的 @return 以及 @author,加了這些註解之後,就能夠用jdk幫咱們自動生成對應的API文檔了github
編譯檢查:這個也很常見 @Override,並且功能很強大,我將會在之後的文章中介紹框架
進行代碼分析:這是本篇文章的重點。這個和編譯檢查同樣也是一個強大的功能,但相比與編譯檢查因爲其用到了反射,在性能上存在一些問題ide
咱們首先來看段代碼工具
public class MainActivity extends AppCompatActivity { @OnClick(R.id.test_btn) void test(){ test_tv.setText("恭喜您,綁定成功了!"); } @FindViewByID(R.id.test_tv) TextView test_tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bindView(this); } }
這是一個最基本的activity,裏面有2個控件,Button和TextView性能
在我點擊Button後TextView的文字被改變。而我所作的僅僅是ButterKnife.bindView(this)並添加2個註解而已,這樣就實現了控件的綁定,省去了不少與業務無關的代碼,是否是簡潔了不少。學習
看了註解的功能是否是很想了解它是怎麼作到的,接下來我就來看看它是什麼,怎麼用,怎麼利用this
官方把它叫作元數據,即一種描述數據的數據。因此,能夠說註解就是源代碼的元數據。用它來能夠來描述、標記咱們的源代碼。插件
如下是我上文中定義的一個 @OnClick註解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { int value() default 0; }
J2SE5.0版本在 java.lang.annotation提供了四種元註解,專門註解其餘的註解:
@Documented –註解是否將包含在JavaDoc中
@Retention –何時使用該註解
@Target? –註解用於什麼地方
@Inherited – 是否容許子類繼承該註解
@Documented–一個簡單的Annotations標記註解,表示是否將註解信息添加在java文檔中,通常不用管。
@Retention– 定義該註解的生命週期,很重要,必須指定,如下是3種生命週期的介紹
RetentionPolicy.SOURCE – 在編譯階段丟棄。這些註解在編譯結束以後就再也不 有任何意義,因此它們不會寫入字節碼。@Override, @SuppressWarnings都屬 於這類註解。 RetentionPolicy.CLASS – 在類加載的時候丟棄。在字節碼文件的處理中有用。 註解默認使用這種方式。 RetentionPolicy.RUNTIME– 始終不會丟棄,運行期也保留該註解,所以可使 用反射機制讀取該註解的信息。咱們自定義的註解一般使用這種方式。
@Target – 表示該註解用於什麼地方。若是不明確指出,該註解能夠放在任何地方。如下是一些可用的參數。須要說明的是:屬性的註解是兼容的,若是你想給7個屬性都添加註解,僅僅排除一個屬性,那麼你須要在定義target包含全部的屬性。
ElementType.TYPE:用於描述類、接口或enum聲明 ElementType.FIELD:用於描述實例變量 ElementType.METHOD ElementType.PARAMETER ElementType.CONSTRUCTOR ElementType.LOCAL_VARIABLE ElementType.ANNOTATION_TYPE 另外一個註釋 ElementType.PACKAGE 用於記錄java文件的package信息
@Inherited – 定義該註釋和子類的關係
Annotations只支持基本類型、String及枚舉類型。註釋中全部的屬性被定義成方法,並容許提供默認值。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Book{ public enum Priority {LOW, MEDIUM, HIGH} String author() default "Yash"; int price() default 20; Status status() default Status.NOT_STARTED; }
看看怎麼用它
@Todo(priority = Todo.Priority.MEDIUM, author = "zsq", status = Todo.Status.STARTED) public void incompleteMethod1() { }
經過字段名 = 的形式給字段賦值,若是沒賦值,則使用缺省值。若是註解中只有一個屬性,能夠直接命名爲「value」,使用時無需再標明屬性名,例如我定義的 @OnClick註解。
咱們定義了本身的註解並將其應用在業務邏輯的方法上。如今咱們須要寫一個用戶程序調用咱們的註解。這裏咱們須要使用反射機制。若是你熟悉反射代碼,就會知道反射能夠提供類名、方法和實例變量對象。全部這些對象都有getAnnotation()這個方法用來返回註解信息。咱們須要把這個對象轉換爲咱們自定義的註釋(使用 instanceOf()檢查以後),同時也能夠調用自定義註釋裏面的方法。
重要的API說3遍,另外用到的幾個方法也很重要,下面的代碼會演示,更多的API使用參考能夠去查閱JDK文檔。
具體到咱們本編文章的實例,調用註解的傢伙就是咱們剛剛在MainActivity裏用到的 ButterKnife,咱們經過設置監聽的註解來看看它到底作了什麼
public static final void bindView(final Activity activity){ traversalMethod(activity); traversalField(activity); }
在咱們調用的ButterKnife.bindView(this)中咱們拿到了MainActivity的實例,而且經過反射遍歷裏面全部的方法:
private static void traversalMethod(final Activity activity) { Method[] methodArray = getObjectMethodArray(activity); for (final Method method:methodArray){ if(isAnnotationPresent(method)){ int viewID = getViewID(method); setOnClickListenerForControl(activity, method, viewID); } } } private static Method[] getObjectMethodArray(Activity activity) { return activity.getClass().getMethods(); }
接着判斷方法是否被咱們註解:
private static boolean isAnnotationPresent(Method method) { return method.isAnnotationPresent(OnClick.class); }
若是是咱們用註解標註的方法則經過註解獲取註解裏保存的空間ID,而且經過MainActivity的實例爲其設置點擊監聽,在監聽內調用被註解標註的方法。
private static int getViewID(Method method) { return method.getAnnotation(OnClick.class).value(); } private static void setOnClickListenerForControl(final Activity activity, final Method method, int viewID) { activity.findViewById(viewID).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { try { method.invoke(activity); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); }
咱們用反射獲取註解的方式實現了ButterKnife的功能,但文章開頭說過反射的存在性能上的不足。而實際上ButterKnife自己用的也不是反射,而是用的apt工具在編譯時期就能夠獲取到全部的方法、字段、以及他們的註解,從而避免了使用反射,解決了性能的問題。接下來的文章我會講解本文開頭提到的第三點,也就是ButterKnife實際使用的方法,將咱們本身的ButterKnife改成ButterKnife官方的實現方法。
如下github地址是本篇文章講解用到的demo:
https://github.com/sally519/MyButterKnief