java 注解 android:ButterKnife

一、基本概念

注解(Annotation),也叫元數(shù)據(jù)。一種代碼級別的說明。它是JDK1.5及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。

我們都知道在Java代碼中使用注釋是為了提升代碼的可讀性,也就是說,注釋是給人看的(對于編譯器來說沒有意義)。注解可以看做是注釋的“強力升級版",它可以向編譯器、虛擬機等解釋說明一些事情(也就是說它對編譯器等工具也是“可讀”的)。比如我們非常熟悉的@Override注解,它的作用是告訴編譯器它所注解的方法是重寫的父類中的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。

也就是說,注解是描述Java代碼的代碼,它能夠被編譯器解析,注解處理工具在運行時也能夠解析注解。除了向編譯器等傳遞一些信息,我們也可以使用注解生成代碼。比如我們可以使用注解來描述我們的意圖,然后讓注解解析工具來解析注解,以此來生成一些”模板化“的代碼。比如Hibernate、Spring等框架大量使用了注解,來避免一些重復的工作。注解是一種”被動“的信息,必須由編譯器或虛擬機來“主動”解析它,它才能發(fā)揮自己的作用。

使用Annotation之前(甚至在使用之后),XML被廣泛的應用于描述元數(shù)據(jù)。不知何時開始一些應用開發(fā)人員和架構師發(fā)現(xiàn)XML的維護越來越糟糕了。他們希望使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是松耦合的(在某些情況下甚至是完全分離的)代碼描述。如果你在Google中搜索“XML vs. annotations”,會看到許多關于這個問題的辯論。最有趣的是XML配置其實就是為了分離代碼和配置而引入的。上述兩種觀點可能會讓你很疑惑,兩者觀點似乎構成了一種循環(huán),但各有利弊。下面我們通過一個例子來理解這兩者的區(qū)別。

假如你想為應用設置很多的常量或參數(shù),這種情況下,XML是一個很好的選擇,因為它不會同特定的代碼相連。如果你想把某個方法聲明為服務,那么使用Annotation會更好一些,因為這種情況下需要注解和方法緊密耦合起來,開發(fā)人員也必須認識到這點。

另一個很重要的因素是Annotation定義了一種標準的描述元數(shù)據(jù)的方式。在這之前,開發(fā)人員通常使用他們自己的方式定義元數(shù)據(jù)。例如,使用標記interfaces,注釋,transient關鍵字等等。每個程序員按照自己的方式定義元數(shù)據(jù),而不像Annotation這種標準的方式。

目前,許多框架將XML和Annotation兩種方式結(jié)合使用,平衡兩者之間的利弊。

二、元注解

元注解即用來描述注解的注解。Java5.0定義的元注解:

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

1.@Target
用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)。取值(ElementType)有:
1.CONSTRUCTOR:用于描述構造器 
2.FIELD:用于描述實例變量
3.LOCAL_VARIABLE:用于描述局部變量 
4.METHOD:用于描述方法 
5.PACKAGE:用于記錄java文件的package信息
6.PARAMETER:用于描述參數(shù) 
7.TYPE:用于描述類、接口(包括注解類型) 或enum聲明

2.@Retention
表示需要在什么級別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效)。取值(RetentionPoicy)有:

  • SOURCE:
    在源文件中有效(即源文件保留),在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義,所以它們不會寫入字節(jié)碼。@Override, @SuppressWarnings都屬于這類注解。
  • CLASS:
    在class文件中有效(即class保留),在類加載的時候丟棄。在字節(jié)碼文件的處理中有用。注解默認使用這種方式。
  • RUNTIME:
    在運行時有效(即運行時保留),始終不會丟棄,運行期也保留該注解,因此可以使用反射機制讀取該注解的信息。我們自定義的注解通常使用這種方式。

3.@Documented
****@****Documented用于描述其它類型的annotation應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記注解,沒有成員。

**4.@Inherited **
@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation將被用于該class的子類。
  注意:@Inherited annotation類型是被標注過的class的子類所繼承。類并不從它所實現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
  當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達類繼承結(jié)構的頂層。

三、自定義注解

參考
扯扯Java反射與注解
怎樣理解 java 注解和運用注解編程
怎樣優(yōu)雅地使用java注解
retrofit 注解學習

要獲取類方法和字段的注解信息,必須通過Java的反射技術來獲取 Annotation對象,因為你除此之外沒有別的獲取注解對象的方法。
<pre>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Example{
public static String STRING_DATA = "String";
public String name() default "New Example";
public String description();
public int id() default 0;
}
</pre>

自定義注解方式與JAVA接口的定義非常相似。不同的是,interface關鍵字前面加上了 “@”來作出區(qū)分這是注解而不是接口。并且注解定義中你可以看到在 name()和id()后面都有一個" default“,這表示當你使用該注解時,如果你沒有指定這項的值,它將會使用default后面的作為默認值。而注解上方的另外兩個注解則指定了注解的有效策略和注解對應的對象類型。

<pre>
//IConfig.java:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IConfig {
public String name();
public String type();
public static String DInteger = "Integer";
public static String DDouble = "Double";
public static String DString = "String";
public static String DLong = "Long";
public static String DChar = "Character";
}

//status.java:
public class Status {
@IConfig(name = "ontime.starttime", type = IConfig.DInteger)
public static int data = 1;
public static final int d = 2;
}

//test:
public static void main(String[] args) throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, SecurityException, InvocationTargetException{
//加載這個類
Class<?> cls = Class.forName("cn.sunflyer.test.Status");
//這里是獲取類中的全部公開的變量 即變量修飾符需要為public
Field fs[] = cls.getFields();
if(fs != null){
//遍歷變量數(shù)組
for(Field x:fs){
//打印出變量名稱和修飾符的值,可以通過Modifier類中的常量對比獲取修飾符類型
System.out.println("名稱 " + x.getName() + " 修飾符 " + x.getModifiers());
Annotation rms = x.getAnnotation(IConfig.class);
//如果獲取到IConfig注解,則輸出內(nèi)容并根據(jù)注解動態(tài)修改數(shù)據(jù)
if(rms != null){
IConfig ic = (IConfig)rms;
System.out.println("注解 name : " + ic.name());
System.out.println("注解 type : " + ic.type());
String da = "1234";
Object ras = null;
if(ic.type().equals(IConfig.DString)){
ras = da;
}else{
//加載JAVA基本數(shù)據(jù)類型的封裝對象類
Class<?> fCls = Class.forName("java.lang." + ic.type());
//獲取指定方法
Method ms = fCls.getMethod("valueOf", String.class);
//調(diào)用方法
ras = ms.invoke(null , da);
}
x.set(null, ras);
}
}
}
}
</pre>

運行后的結(jié)果為:
名稱 data 修飾符 9
注解 name : ontime.starttime
注解 type : Integer
名稱 d 修飾符 25

四、android注解 ButterKnife

ButterKnife框架原理
深入理解 ButterKnife,讓你的程序?qū)W會寫代碼
Android注解那些事兒
專門為Android View設計的綁定注解ButterKnife
偷懶插件Android Butterknife Zelezny
使用 @Bind注解并傳入一個View ID,Butter Knife 就可以找到并且自動地對你的布局中的View進行轉(zhuǎn)換并綁定到類成員上。

class ExampleActivity extends Activity {
  @Bind(R.id.title) TextView title;
  @Bind(R.id.subtitle) TextView subtitle;
  @Bind(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

請注意,相比與緩慢的反射機制,Butter Knife的代碼是生成的,因此不必擔心注解的性能問題??赡芎芏嗳硕加X得ButterKnife在bind(this)方法執(zhí)行的時候通過反射獲取ExampleActivity中所有的帶有@Bind注解的屬性并且獲得注解中的R.id.xxx值,最后還是通過反射拿到Activity.findViewById()方法獲取View,并賦值給ExampleActivity中的某個屬性.這是一個注解庫的實現(xiàn)方式,比較原始,一個很大的缺點就是在Activity運行時大量使用反射會影響App的運行性能,造成卡頓以及生成很多臨時Java對象更容易觸發(fā)GC。ButterKnife顯然沒有使用這種方式,它用了Java Annotation Processing技術,就是在Java代碼編譯成Java字節(jié)碼的時候就已經(jīng)處理了。

為什么你用@Bind、@OnClick等注解標注的屬性或方法必須是public或protected的,因為ButterKnife是通過ExampleActivity.this.editText
來注入View的。

為什么要這樣呢?有些注入框架比如roboguice你是可以把View設置成private的,答案就是性能。如果你把View設置成private,那么框架必須通過反射來注入View,不管現(xiàn)在手機的CPU處理器變得多快,如果有些操作會影響性能,那么是肯定要避免的,這就是ButterKnife與其他注入框架的不同。

調(diào)用bind來生成這些代碼,你可以查看或調(diào)試這些代碼。
例如上面的例子,生成的代碼大致如下所示:

public void bind(ExampleActivity activity) {
  activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
  activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
  activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}

綁定資源到類成員上可以使用@BindBool、@BindColor、@BindDimen、@BindDrawable、@BindInt、@BindString。使用時對應的注解需要傳入對應的id資源,例如@BindString你需要傳入R.string.id_string的字符串的資源id。

class ExampleActivity extends Activity {
  @BindString(R.string.title) String title;
  @BindDrawable(R.drawable.graphic) Drawable graphic;
  @BindColor(R.color.red) int red; // int or ColorStateList field
  @BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
  // ...
}

偵聽器綁定

@OnClick(R.id.submit)
public void submit() {
  // TODO submit data to server...
}

@OnClick(R.id.submit)
public void sayHi(Button button) {
  button.setText("Hello!");
}

//同時指定多個id的控件到同一個事件監(jiān)聽上:

@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
  if (door.hasPrizeBehind()) {
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();
  } else {
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();
  }
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,752評論 15 116
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 8,816評論 4 31
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,413評論 0 2
  • 今天沒有午休,大腦犯困,下午的第二節(jié)課偶然的閃過了高一跟小坤斗嘴慪氣的小時光。 那時的我們是三個人一排靠...
    奚夷閱讀 335評論 6 0
  • me:媽媽捏的橡皮泥在哪呢? 星:可能它去旅游了吧,想到外面的世界看看。 me:我剛剛還看到它在這呢,不會去旅游的...
    星辰媽咪閱讀 626評論 0 0

友情鏈接更多精彩內(nèi)容