66.源碼解析:ButterKnife(7.0.1)

1.使用

2.預備知識:

(1)註解html

元註解是指註解的註解。包括  @Retention @Target @Document @Inherited四種。java

1.一、@Retention: 定義註解的保留策略api

@Retention(RetentionPolicy.SOURCE)    //註解僅存在於源碼中,在class字節碼文件中不包含
@Retention(RetentionPolicy.CLASS)       // 默認的保留策略,註解會在class字節碼文件中存在,但運行時沒法得到,
@Retention(RetentionPolicy.RUNTIME)   // 註解會在class字節碼文件中存在,在運行時能夠經過反射獲取到
1.二、@Target:定義註解的做用目標
@Target(ElementType.TYPE)    //接口、類、枚舉、註解
@Target(ElementType.FIELD)  //字段、枚舉的常量
@Target(ElementType.METHOD)  //方法
@Target(ElementType.PARAMETER)  //方法參數
@Target(ElementType.CONSTRUCTOR)   //構造函數
@Target(ElementType.LOCAL_VARIABLE) //局部變量
@Target(ElementType.ANNOTATION_TYPE) //註解
@Target(ElementType.PACKAGE)  //包   
 它的elementType 能夠有多個,一個註解能夠爲類的,方法的,字段的等等
1.三、@Document:說明該註解將被包含在javadoc中
1.四、@Inherited:說明子類能夠繼承父類中的該註解

(2)反射
一、java的反射機制的定義:
在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
二、獲取Class對象的3種方式:
(1)經過Object類的getClass()方法。例如
Class c1 = new String("").getClass();
(2)經過Class類的靜態方法——forName()來實現:
Class c2 = Class.forName("MyObject");
(3)若是T是一個已定義的類型的話,在java中,它的.class文件名:T.class就表明了與其匹配的Class對象,例如:
Class c3 = Manager.class;
Class c4 = int.class;
Class c5 = Double[].class;
三、Class類中的6個重要的方法:
1.getName()
一個Class對象描述了一個特定類的特定屬性,而這個方法就是返回String形式的該類的簡要描述。因爲歷史緣由,對數組的Class對象
調用該方法會產生奇怪的結果。
2.newInstance()
該方法能夠根據某個Class對象產生其對應類的實例。須要強調的是,它調用的是此類的默認構造方法。例如:
MyObject x = new MyObject();
MyObject y = x.getClass().newInstance();
3.getClassLoader()
返回該Class對象對應的類的類加載器。
4.getComponentType()
該方法針對數組對象的Class對象,能夠獲得該數組的組成元素所對應對象的Class對象。例如:
int[] ints = new int[]{1,2,3};
Class class1 = ints.getClass();
Class class2 = class1.getComponentType();
而這裏獲得的class2對象所對應的就應該是int這個基本類型的Class對象。
5.getSuperClass()
返回某子類所對應的直接父類所對應的Class對象。
6.isArray()
斷定此Class對象所對應的是不是一個數組對象。
此例子中用到了2個很牛逼的方法:
四、關於Field的2個牛逼方法:
//獲取類的屬性x,無視權限
1.getClass().getDeclaredField("x");
//設置屬性可編輯
2.setAccessible(true);
五、關於Method的1個牛逼方法:invoke
Method 方法=類.getMethod(方法名,參數類型)
返回值=方法.invoke(對象,參數)
等價於不用反射的:
返回值=對象.方法(參數)

測試Reflect:
/*java反射用法*/
public class TestReflect extends InstrumentationTestCase {

public void testMain() {
LogUtil.e("測試反射:[START]");
try {
main();
} catch (Exception e) {
LogUtil.e("出錯啦:e=" + e.getMessage());
}
LogUtil.e("測試反射:[END]");
}

//★這裏說的Field都是 類 身上的,不是實例上的
public static void main() throws Exception {
Point pt1 = new Point(3, 5);
//獲得一個字段
Field fieldY = pt1.getClass().getField("y"); //y 是變量名
//fieldY的值是5麼?? 大錯特錯
//fieldYpt1根本沒有什麼關係,你看,是pt1.getClass(),是 字節碼 啊
//不是pt1對象身上的變量,而是類上的,要用它取某個對象上對應的值
//要這樣
LogUtil.e(fieldY.get(pt1).toString()); //這纔是5

//如今要x

/*
Field fieldX = pt1.getClass().getField("x"); //x 是變量名
LogUtil.e(fieldX.get(pt1));
*/

//運行 報錯 私有的,找不到
//NoSuchFieldException
//說明getField 只能夠獲得 公有的
//怎麼獲得私有的呢??

/*
Field fieldX = pt1.getClass().getDeclaredField("x"); //這個管你公的私的,都拿來
//而後輪到這裏錯了
// java.lang.IllegalAccessException:
//Class com.ncs.ReflectTest can not access a member of class com.ncs.Point with modifiers "private"
LogUtil.e(fieldX.get(pt1));
*/

//三步曲 一是不讓你知道我有錢 二是把錢晃一下,不給用 三是暴力搶了

//暴力反射
Field fieldX = pt1.getClass().getDeclaredField("x"); //這個管你公的私的,都拿來
fieldX.setAccessible(true);//上面的代碼已經看見錢了,開始搶了
LogUtil.e(fieldX.get(pt1).toString());

//out 3 OK!!


}

public static class Point {

private int x;
public int y;

public String s1 = "ball";
public String s2 = "hubin";
public String s3 = "zhangxiaoxiang";
//作實驗而已,字段不多是 public

public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}

}
}
運行結果以下:

測試Invoke:
/*java反射中Methodinvoke方法的用法*/
public class TestReflect extends InstrumentationTestCase {

private String name;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public int add(int param1, int param2) {
return param1 + param2;
}

public String echo(String mesg) {
return "echo" + mesg;
}

public void testMain() {
LogUtil.e("測試反射:[START]");
Class classType = TestReflect.class;
try {
Object invokertester = classType.newInstance(); //1
Method addMethod = classType.getMethod("add", new Class[]{ //2
int.class, int.class
});
Object result = addMethod.invoke(invokertester, new Object[]{ //3
new Integer(100), new Integer(200)
});
LogUtil.e(result.toString());

Method echo = classType.getMethod("echo", new Class[]{String.class});
Object obj = echo.invoke(invokertester,
new Object[]{new String("jy is very good!!!")});
LogUtil.e(obj.toString());


TestReflect test = new TestReflect(); //1
test.setName("小明"); //2
Method[] methods = test.getClass().getDeclaredMethods(); //3
//循環查找獲取id方法,並執行查看是否有返回值
for (int i = 0; i < methods.length; i++) {
//若是此方法有getId關鍵字則執行
if (methods[i].getName().indexOf("get") != -1 && methods[i].getName().indexOf("Name") != -1) {
try {
// 獲取此get方法返回值,判斷是否有值,若是沒有值說明即將執行的操做新增
if (methods[i].invoke(test, null) == null) { //4
LogUtil.e("此對象沒有值!!!");
} else {
Object strName = methods[i].invoke(test, null);
LogUtil.e(strName.toString());
}
} catch (Exception e) {
System.out.print("");
}
}
}
} catch (Exception ex) {
LogUtil.e("出錯啦:e=" + ex.getMessage());
}
LogUtil.e("測試反射:[END]");
}
}
運行結果以下:

3.源碼解析:

(1)綁定視圖id
(2)綁定點擊事件
OnClick類第28行,調用了另外一個自定義註解
OnClick類第32行,也調用了另外一個自定義註解

(3)在activity.onCreate中初始化時

首先,bind方法的第317行,會調用findViewBinderForClass,獲取ViewBinder對象
而後,bind方法的第319行,會調用ViewBinder對象的bind方法

坑爹啊,竟然是接口,那麼,它的實現類在哪呢?暫且不表,下回細說。
先深刻 findViewBinderForClass方法
第33九、340行,會用反射建立一個ViewBinder對象,類名爲:原類名+
這個玩意應該就是ViewBinder的實現類了,可是我找遍ButterKnife的源碼,都找不到該實現類的源碼。
因而,源碼解析卡在這裏了。後來,看了 AbstractProcessor的相關知識,才知道,註解能夠分爲:運行時註解、編譯時註解,
運行時註解就是就是運行時運用反射,動態獲取對象、屬性、方法等,通常的IOC框架就是這樣,可能會犧牲一點效率。
然而,大名鼎鼎的ButterKnife運用的是編譯時註解。
編譯時註解就是在程序編譯時根據註解進行一些額外的操做,ButterKnife在咱們編譯時,就根據註解,自動生成了一些輔助類。
入口爲 AbstractProcessor的process方法。好,終於又有方向,繼續深刻。

(4)編譯時註解 ,ButterKnifeProcessor會自動根據註解生成輔助類
在源碼中找到了AbstractProcessor的子類:ButterKnifeProcessor
有點坑爹,源碼中 AbstractProcessor等類會標紅,好奇怪,我明明程序能夠好好的運行呀,怎麼會找不到這些類呢?javax不是jdk自帶的api嗎?
算了,先無論這了,繼續看。
其中,process方法的129行,調用了brewJava方法,哈哈,就是這個方法,會自動建立java文件並寫入代碼
我將程序編譯後,在
目錄下找到了自動建立的java文件,以下:
看這個類的名稱,「$$ViewBinder」,是否是有印象。嘿嘿,ViewBinder的實現類,終於找到了。
第11行,調用 findRequiredView方法
首先,調用findView,找到View類型的目標對象
坑爹啊,抽象方法,那麼實現方法在哪呢?再次短路!
好吧,暫時略過,先去看看轉換View類型的方法,發現其實就是強轉而已

繼續尋找findView的實現類,全局搜索「findView(」,而後發現:

上面的抽象方法findView其實也在枚舉類Finder中,好吧,原來枚舉類中能夠定義抽象方法,並且,枚舉值,還能夠實現它的抽象方法,表示又學到了一招。
咱們傳入的爲Activity,因而,見第100行,哈哈,熟悉的findViewById終於看到了。好!ButterKnife的@bind註解的流程已經走通了,下面再看@OnClick的流程:

返回去看那個自動生成的$$ViewBinder類,

見第17行, DebouncingOnClickListener即爲點擊事件的監聽器,

第26行,在onClick方法中,調用了抽象方法doClick,實現方法在哪呢?在自動生成的輔助類中,見$$ViewBinder類的第18行。

實現方法中,調用了target.doMyClick(po),嘿嘿,回去看咱們的使用類LoginActivity,見第44行,好!ButterKnife的@bind註解的流程也走通了!

那麼,到這就大功告成了嗎?不!
如今咱們只知道,ButterKnife在編譯時,根據註解,自動生成了一個輔助類,這個輔助類,幫咱們搞定了findViewById和OnClick!
可是,這個輔助類的生成細節,咱們還不是很清楚。

(5)分析ButterKnifeProcessor自動生成代碼的細節

經過前面的分析,咱們知道,自動生成主要涉及到兩個方法:
入口方法process
寫碼方法brewJava

再次回顧下最終生成的輔助類:

先看與這個類聯繫最緊密的brewJava方法吧:

包名、導入類、類名的生成,均可以一目瞭然,問題是:bind和unbind方法裏面的細節,這些都是和咱們本身寫的代碼緊密聯繫的,它是怎麼知道咱們的字段名和方法名的?
不得不說,反射和註解真是太牛逼了,也許有一天,真的就能夠徹底用機器寫代碼了。好了,先不感慨了,繼續看代碼:
第104行,調用emitBindMethod方法
第106行,調用emitUnbindMethod方法

第127行,遍歷id,調用emitViewBindings方法

第198行,看到了輔助類的11行的東東,而後調用了emitHumanDescription方法

其實這個方法沒什麼用,看看就過去吧。
—————————————【未完待續,我是分割線】———————————————————————


再看process:

第120行,調用findAndParseTargets方法,查找並解析目標。
不得不說,大牛的代碼真的是不用太多的註釋的,代碼自己就是註釋了,實乃吾輩學習楷模!

其實就是根據不一樣的註解,分別遍歷,這裏咱們只分析@Bind和@OnClick,因此就只看第148行的parseBind和第156行的findandParseListener了,
先看parseBind,由於咱們的使用類中@Bind的參數只是一個id,這裏就只看parseBindOne

—————————————【未完待續,我是分割線】———————————————————————

參考:



相關文章
相關標籤/搜索