Android註解式綁定控件,沒你想象的那麼難

摘要

本文原創,轉載請以鏈接形式註明地址:http://kymjs.com/code/2014/12/05/03
Android開發中,有一個讓人又愛又恨的方法叫findViewById(int);我想如果你是一民Android開發者,必然知道這個方法。 那麼爲什麼讓人又愛又恨呢?想必大家也是很有感觸。

吐槽

Android開發中,有一個讓人又愛又恨的方法叫findViewById(int);我想如果你是一民Android開發者,必然知道這個方法,

爲什麼說findViewById(int);讓人又愛又恨呢?想必大家也是很有感觸。
寫一個佈局,用Java代碼寫和用xml文件寫,完成速度完全是無法比擬的。xml佈局太方便了。
同樣的,想獲取一個控件的對象,如果你是使用的xml佈局文件寫的佈局,那麼你必須調用findViewById()這個方法。

TextView t = (TextView) findViewById(R.id.x);

這是我們最常見的 獲取xml佈局中一個textview對象的過程。
那麼問題就來了,這特麼奇葩的方法名也太長了吧!!!好吧,其實人家名字起的也沒有錯,要描述清楚這函數的含義,也必須這麼多個字母。

可是你丫的返回一個View讓我用的時候還得強轉,這也太麻煩了吧。我一行代碼總共也就80列(Eclipse默認),縮進八格(方法寫在類裏面,語句寫在方法裏面),
就算像上面的例子textView對象只有一個字母,id也只有一個字母,這一個初始化也要佔我54列了。要是變量名再長點,縮進層次再深點,這一個初始化就兩行了。
一個界面至少也有四個控件吧,這麼複雜的初始化,太坑爹了。
有問題總會有對應的解決辦法,下面我就向大家介紹一下KJFrameForAndroid框架使用註解解決這種麻煩。

瞭解註解:

從jdk1.5開始,Java提供了註解的功能,允許開發者定義和使用自己的註解類型,該功能由一個定義註解類型的語法和描述一個註解聲明的語法,讀取註解的API,一個使用註解修飾的class文件和一個註解處理工具組成。
首先,你需要接受一個關鍵字@interface ,噢,它可不是接口定義關鍵字,更不是OC裏面的@interface關鍵字,是Java中表示聲明一個註解類的關鍵字。
使用@interface表示我們已經繼承了java.lang.annotation.Annotation類,這是一個註解的基類接口,就好像Object類,現在你只需要知道它的存在就行了。
還有一條規定:在定義註解時,不能繼承其他的註解或接口。
那麼,這就是最簡單的一個註解類

public @interface MyAnnotation {
}

然而通常在使用時我們都會給這個註解類加上兩個註解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)

ElementType、RetentionPolicy是兩個枚舉類,ElementType.FIELD表示我們需要註解的是一個字段,以下是摘自JDK1.6文檔中的介紹:

使用註解:

以下爲KJFrameForAndroid框架中綁定控件註解部分的定義與使用:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
public int id();
public boolean click() default false;
}

@BindView(id = R.id.x, click = true)
private TextView t;

我們可以看到,除了明顯減少了代碼量,還使得代碼結構更加清晰。
其中,定義部分的id() 表示註解接受一個int類型的數據作爲id所對應的值(就如使用中的id = R.id.xxx);
同理,定義部分的click表示接受一個Boolean類型的數據作爲click對應的值,還可以設置一個默認值使用default修飾;

處理註解:

我們已經知道了註解怎麼定義和使用,接下來就應該知道怎麼處理了。
上面已經說了,bindview註解可以接受一個int類型的值和一個Boolean類型的值,那麼這兩個值接受了以後如何獲取呢?
其實獲取的方式很簡單就是通過一個BindView類型的對象,調用這個對象來自聲明中定義的兩個方法——>id()或click()方法。
現在就有一個問題了,註解類型是不能直接new對象的,那麼這個BindView對象從哪裏來呢?
這時就需要用到Java的反射機制。我們知道,每一個繼承自Object類的類都會繼承一個getClass()方法,下面看一下這個方法的原型:

/**
* Returns the unique instance of {@link Class} that represents this
* object's class. Note that {@code getClass()} is a special case in that it
* actually returns {@code Class<? extends Foo>} where {@code Foo} is the
* erasure of the type of the expression {@code getClass()} was called upon.
* <p>
* As an example, the following code actually compiles, although one might
* think it shouldn't:
* <p>
* <pre>{@code
*   List<Integer> l = new ArrayList<Integer>();
*   Class<? extends List> c = l.getClass();}</pre>
*
* @return this object's {@code Class} instance.
*/
public final native Class<?> getClass();

是一個native方法,根據註釋我們知道,這個方法返回的是該類的Class對象,同時也是該類的二進制對象。
Class中有一個方法叫getDeclaredFields(),是用來返回這個類的全部字段,返回類型是Field[]
通過Field對象的getAnnotation(Class<?>)方法,我們可以獲取到任何一個Class的對象,通過getAnnotation(Class<?>),我們就可以獲取到BindView的對象了。

例如:

Field[] fields = currentClass.getClass().getDeclaredFields();
for(int i = 0; i &lt; fields.length; i++){

BindView bindView = field.getAnnotation(BindView.class);

int viewId = bindView.id();  //這是我們傳的id

boolean clickLis = bindView.click(); //這是我們傳的click
}

在Android項目中應用:

至此,我們已經瞭解了註解,並且知道怎麼使用,怎麼處理註解了,現在只剩下最後一個問題:在項目中使用。
很簡單,傳一個Activity對象,調用findViewById()不就行了。
於是,我們可以這樣
activity.findViewById( bindView.id() );
最後在我們的Activity中調用這個函數就OK了。

以下是Android應用框架KJFrameForAndroid中使用註解綁定控件的核心代碼:

/**
* @param currentClass
*            當前類,一般爲Activity或Fragment
* @param sourceView
*            待綁定控件的直接或間接父控件
*/
public static void initBindView(Object currentClass, View sourceView) {
  // 通過反射獲取到全部屬性,反射的字段可能是一個類(靜態)字段或實例字段
  Field[] fields = currentClass.getClass().getDeclaredFields();
  if (fields != null; fields.length > 0) {
    for (Field field : fields) {
      // 返回BindView類型的註解內容
      BindView bindView = field.getAnnotation(BindView.class);
      if (bindView != null) {
        int viewId = bindView.id();
        boolean clickLis = bindView.click();
        try {
          field.setAccessible(true);
          if (clickLis) {
            sourceView.findViewById(viewId).setOnClickListener(
            (OnClickListener) currentClass);
          }
          // 將currentClass的field賦值爲sourceView.findViewById(viewId)
          field.set(currentClass, sourceView.findViewById(viewId));
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }
}

其實安卓中的註解式綁定控件(也是所謂的IOC控制反轉在安卓中的一種應用)其實本質的使用就是Java基礎中反射的使用。值得一提的是,反射執行的效率是很低的
如果不是必要,應當儘量減少反射的使用,因爲它會大大拖累你應用的執行效率。
順帶一提:我一直很排斥註解,因爲類反射的效率太低了。現在有很多安卓應用開發框架,比如KJFrameForAndroid, xUtils, afinal, thinkAndroid,這些框架都是使用反射來起到註解綁定控件。
更有的框架甚至是一切東西都使用註解去完成,我只能說註解便捷,但請慎用。

發佈了39 篇原創文章 · 獲贊 17 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章