在我的上一篇文章中,絕對不容錯過,ButterKnife使用詳談中,講解了對ButterKnife的使用。這篇文章將接著一篇文章使用之後,對ButterKnife的工作流程進行概要分析。這裡Butterknife分析來自參考自連結How ButterKnife actually works?,並作出部分修改。這裡做一個整理和學習。
考慮到ButterKnife的入口使用java註解,比如。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ExampleActivity extends Activity { @Bind(R.id.user) EditText username; @Bind(R.id.pass) EditText password; @OnClick(R.id.submit) void submit() { // TODO call server… } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields… } } |
首先這裡會對java註解(java annotation)分析,然後面接上Butterknife的工作流程。
1 什麼是java註解?
在java語法中,使用@符號作為開頭,並在@後面緊跟註解名。被運用於類,介面,方法和欄位之上,例如:
1 2 3 4 |
@Override void myMethod() { ...... } |
這其中@Override就是註解。這個註解的作用也就是告訴編譯器,myMethod()方法覆寫了父類中的myMethod()方法。
2 java中內建的註解
java中有三個內建的註解:
1 2 3 |
@Override,表示當前的方法定義將覆蓋超類中的方法,如果出現錯誤,編譯器就會報錯。 @Deprecated:如果使用此註解,編譯器會出現警告資訊。 @SuppressWarnings:忽略編譯器的警告資訊。 |
2.1 @Override 註解
當我們的子類覆寫父類中的方法的時候,我們使用這個註解,這一定程度的提高了程式的可讀性也避免了維護中的一些問題,比如說,當修改父類方法簽名(方法名和引數)的時候,你有很多個子類方法簽名也必須修改,否則編譯器就會報錯,當你的類越來越多的時候,那麼這個註解確實會幫上你的忙。如果你沒有使用這個註解,那麼你就很難追蹤到這個問題。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MyParentClass { public void justaMethod() { System.out.println("Parent class method"); } } public class MyChildClass extends MyParentClass { @Override public void justaMethod() { System.out.println("Child class method"); } } |
2.2 @Deprecated註解
一個棄用的元素(類,方法和欄位)在java中表示不再重要,它表示了該元素將會被取代或者在將來被刪除。
當我們棄用(deprecate)某些元素的時候我們使用這個註解。所以當程式使用該棄用的元素的時候編譯器會彈出警告。當然我們也需要在註釋中使用@deprecated標籤來標示該註解元素。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class DeprecatedDemo { /* @deprecated This field is replaced by * MAX_NUM field */ @Deprecated int num=10; final int MAX_NUM=10; /* @deprecated As of release 1.5, replaced * by myMsg2(String msg, String msg2) */ @Deprecated public void myMsg(){ System.out.println("This method is marked as deprecated"); } public void myMsg2(String msg, String msg2){ System.out.println(msg+msg2); } public static void main(String a[]){ DeprecatedDemo obj = new DeprecatedDemo(); obj.myMsg(); System.out.println(obj.num); } } |
2.3 @SuppressWarnings註解
當我們想讓編譯器忽略一些警告資訊的時候,我們使用這個註解。比如在下面這個示例中,我們的deprecatedMethod()方法被標記了@Deprecated註解,所以編譯器會報警告資訊,但是我們使用了@SuppressWarnings(“deprecation”)也就讓編譯器不在報這個警告資訊了。
1 2 3 4 |
@SuppressWarnings("deprecation") void myMethod() { myObject.deprecatedMethod(); } |
3 自定義註解
3.1 來看看一個自定義註解
可以通過下面這種形式新增自己的自定義註解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Target(ElementType.METHOD) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomAnnotation{ int studentAge() default 18; String studentName(); String stuAddress(); String stuStream() default "CSE"; } |
自定義註解使用@interface來宣告一個註解,這裡對這個自定義的註解進行使用:
1 2 3 4 5 6 7 |
@MyCustomAnnotation( studentName="Chaitanya", stuAddress="Agra, India" ) public class MyClass { ... } |
可以看到上面的studentAge 和stuStream欄位已經在註解定義階段設定了預設值(當然也可以對這些預設值進行修改),studentName和stuAddress沒有預設值,在使用的時候必須定義值。
3.2 元註解
在上面的註解定義階段,你一定注意到了這四個註解,那麼他們是什麼呢?
(1).@Target,
(2).@Retention,
(3).@Documented,
(4).@Inherited
那麼來分別看看這四個註解是什麼意思?
表示該註解用於什麼地方,可能的ElementType引數包括:
CONSTRUCTOR:構造器的宣告
FIELD:域宣告
LOCAL_VARIABLE:區域性變數宣告
METHOD:方法宣告
PACKAGE:包宣告
PARAMETER:引數宣告
TYPE:類,介面或enum宣告
1 2 3 4 5 6 |
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface MyCustomAnnotation { } |
1 2 3 4 5 6 7 |
public class MyClass { @MyCustomAnnotation public void myMethod() { ...... } } |
表示在什麼級別保留此資訊,可選的RetentionPolicy引數包括:
SOURCE:註解僅存在程式碼中,註解會被編譯器丟棄
CLASS:註解會在class檔案中保留,但會被VM丟棄
RUNTIME:VM執行期間也會保留該註解,因此可以通過反射來獲得該註解
1 2 3 4 5 6 |
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @interface MyCustomAnnotation { } |
將註解包含在javadoc中
1 2 3 |
java.lang.annotation.Documented @Documented public @interface MyCustomAnnotation { //Annotation body} |
允許子類繼承父類中的註解
1 2 3 4 |
java.lang.annotation.Inherited @Inherited public @interface MyCustomAnnotation { } |
1 2 3 4 |
@MyCustomAnnotation public class MyParentClass { ... } |
1 2 3 |
public class MyChildClass extends MyParentClass { ... } |
4 ButterKnife工作流程解析
有了上面的java註解的基礎知識,那麼接著學習。
4.1 java 註解工作流程
1 註解是在編譯(compile)時期進行處理的。
2 註解處理器(Annotation Processor)讀取java程式碼處理相應的註解,並且生成對應的程式碼。
3 生成的java程式碼被當做普通的java 類再次編譯。
4 註解處理器不能修改存在java輸入檔案,也不能對方法做修改或者新增。
4.2 ButterKnife對應的註解工作流程
當你編譯你的Android工程時,ButterKnife工程中ButterKnifeProcessor類的process()方法會執行以下操作:
1 開始它會掃描Java程式碼中所有的ButterKnife註解@Bind、@OnClick、@OnItemClicked等。
2 當它發現一個類中含有任何一個註解時, ButterKnifeProcessor會幫你生成一個Java類,名字<類名>$$ViewInjector.java,這個新生成的類實現了ViewBinder介面。
3 這個ViewBinder類中包含了所有對應的程式碼,比如@Bind註解對應findViewById(), @OnClick對應了view.setOnClickListener()等等。
4 最後當Activity啟動ButterKnife.bind(this)執行時,ButterKnife會去載入對應的ViewBinder類呼叫它們的bind()方法。
4.3 例項分析
首先ExampleActivity 中使用ButterKnife,如下面所示
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ExampleActivity extends Activity { @Bind(R.id.user) EditText username; @Bind(R.id.pass) EditText password; @OnClick(R.id.submit) void submit() { // TODO call server… } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields… } } |
接著編譯器就會生成ExampleActivity$$ViewBinder.java檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class ExampleActivity$$ViewBinder<T extends com.lgvalle.samples.ui.ExampleActivity> implements ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 21313618, “field ‘user’”); target.username = finder.castView(view, 21313618, “field ‘user’”); view = finder.findRequiredView(source, 21313618, “field ‘pass’”); target.password = finder.castView(view, 21313618, “field ‘pass’”); view = finder.findRequiredView(source, 21313618, “field ‘submit’ and method ‘submit’”); view.setOnClickListener( new butterknife.internal.DebouncingOnClickListener() { @Override public void doClick(android.view.View p0) { target.submit(); } }); } @Override public void reset(T target) { target.username = null; target.password = null; } } |
接著在程式執行的時候,流程就是這樣的
1ButterKnife呼叫findViewBinderForClass
(ExampleActivity.class) 方法查詢ExampleActivity$$ViewBinder.java
2 ExampleActivity$$ViewBinder.bind()方法被執行,查詢view以及處理view的型別轉換,並設定給 ExampleActivity.class的相應屬性(這也就是為什麼相應的屬性在ButterKnife中必須為Public的,因為在這裡會進行訪問。當然如果你不考慮效能,也可以採用反射的方式訪問private的屬性,顯然作者沒有做這麼做)
3 onClickListeners也在 ExampleActivity$$ViewBinder.bind()方法方法中被包裝處理點選事件。
5 參考連結
How ButterKnife actually works?
butterknife 原始碼分析
最新ButterKnife框架原理
Java註解
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式