天天看點

5. Android 架構ButterKnife源代碼分析

一. ButterKnife介紹

在Android程式設計過程中,我們會寫大量的布局和點選事件,像初始view、設定view監聽這樣簡單而重複的操作,這些代碼繁瑣而又不雅觀,比如:

<code>TextView tvSetName = findViewById(R.id.xxx);</code>

<code>tvSetName.setOnClickListener(</code><code>new</code> <code>View.OnClickListener() {</code>

<code>    </code><code>@Override</code>

<code>    </code><code>public</code> <code>void</code> <code>onClick(View v) {</code>

<code>        </code><code>//xxxx</code>

<code>    </code><code>}</code>

<code>});</code>

<code>TextView tvSetAge = findViewById(R.id.xxx);</code>

<code>tvSetAge.setOnClickListener(</code><code>new</code> <code>View.OnClickListener() {</code>

<code>TextView tvSetArea = findViewById(R.id.xxx);</code>

<code>tvSetArea.setOnClickListener(</code><code>new</code> <code>View.OnClickListener() {</code>

Activity中這種代碼多了之後,很不雅觀。

二. 使用簡介

ButterKnife使用方法比較簡單,主要包括以下步驟:

引用ButterKnife包

在onCreate裡面bind(setContentView之後)

綁定各種事件

onDestroy裡面解綁釋放資源

<a href="http://blog.csdn.net/itjianghuxiaoxiong/article/details/50177549" target="_blank">ButterKnife使用方法</a>

三. ButterKnife源代碼下載下傳

<a href="https://github.com/JakeWharton/butterknife" target="_blank">ButterKnife github源代碼位址</a>

直接git clone或者下載下傳zip即可。

四. 編譯

Android studio打開ButterKnife源代碼

AndroidStudio-&gt;File-&gt;open-&gt;ButterKnife源代碼路徑-&gt;确認

Build-&gt;Rebuild Project

五. 生成的aar和jar包

生成的包主要有兩個

butterknife-annotations-8.5.2-SNAPSHOT.jar

路徑:butterknife-annotations-&gt;build-&gt;libs

butterknife-release.aar

路徑: butterknife-&gt;build-&gt;outputs-&gt;aar

六. 其他應用引用自定義ButterKnife包 

删除原來ButterKnife包引用,因為要使用自己編譯的包

拷貝檔案

拷貝上面兩個檔案到自己項目app子產品的libs 目錄

添加aar的關聯

打開app子產品的build.gradle檔案,添加:

<code>compile fileTree(include: ['*.jar'], dir: 'libs')</code>

<code>compile(name: 'butterknife-release', ext: 'aar')</code>

七. 源代碼分析

1. ButterKnife.bind(Activity target)過程

檔案名:ButterKnife.java

<code>static</code> <code>final</code> <code>Map&lt;Class&lt;?&gt;, Constructor&lt;? </code><code>extends</code> <code>Unbinder&gt;&gt; BINDINGS = </code><code>new</code> <code>LinkedHashMap&lt;&gt;();</code>

<code>public</code> <code>static</code> <code>Unbinder bind(</code><code>@NonNull</code> <code>Activity target) {</code>

<code>    </code><code>Log.d(</code><code>"Sandy"</code><code>, </code><code>"ButterKnife bind.. target: "</code> <code>+ target);</code>

<code>    </code><code>View sourceView = target.getWindow().getDecorView();</code>

<code>    </code><code>return</code> <code>createBinding(target, sourceView);</code>

<code>}</code>

<code>private</code> <code>static</code> <code>Unbinder createBinding(</code><code>@NonNull</code> <code>Object target, </code><code>@NonNull</code> <code>View source) {</code>

<code>    </code><code>Class&lt;?&gt; targetClass = target.getClass();</code>

<code>    </code><code>if</code> <code>(debug) Log.d(TAG, </code><code>"Looking up binding for "</code> <code>+ targetClass.getName());</code>

<code>    </code><code>Constructor&lt;? </code><code>extends</code> <code>Unbinder&gt; constructor = findBindingConstructorForClass(targetClass);</code>

<code>    </code><code>if</code> <code>(constructor == </code><code>null</code><code>) {</code>

<code>      </code><code>return</code> <code>Unbinder.EMPTY;</code>

<code>    </code><code>//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.</code>

<code>    </code><code>try</code> <code>{</code>

<code>      </code><code>return</code> <code>constructor.newInstance(target, source);</code>

<code>    </code><code>} </code><code>catch</code> <code>(IllegalAccessException e) {</code>

<code>      </code><code>throw</code> <code>new</code> <code>RuntimeException(</code><code>"Unable to invoke "</code> <code>+ constructor, e);</code>

<code>    </code><code>} </code><code>catch</code> <code>(InstantiationException e) {</code>

<code>    </code><code>} </code><code>catch</code> <code>(InvocationTargetException e) {</code>

<code>      </code><code>Throwable cause = e.getCause();</code>

<code>      </code><code>if</code> <code>(cause </code><code>instanceof</code> <code>RuntimeException) {</code>

<code>        </code><code>throw</code> <code>(RuntimeException) cause;</code>

<code>      </code><code>}</code>

<code>      </code><code>if</code> <code>(cause </code><code>instanceof</code> <code>Error) {</code>

<code>        </code><code>throw</code> <code>(Error) cause;</code>

<code>      </code><code>throw</code> <code>new</code> <code>RuntimeException(</code><code>"Unable to create binding instance."</code><code>, cause);</code>

<code>private</code> <code>static</code> <code>Constructor&lt;? </code><code>extends</code> <code>Unbinder&gt; findBindingConstructorForClass(Class&lt;?&gt; cls) {</code>

<code>    </code><code>Constructor&lt;? </code><code>extends</code> <code>Unbinder&gt; bindingCtor = BINDINGS.get(cls);</code>

<code>    </code><code>if</code> <code>(bindingCtor != </code><code>null</code><code>) {</code>

<code>      </code><code>if</code> <code>(debug) Log.d(TAG, </code><code>"HIT: Cached in binding map."</code><code>);</code>

<code>      </code><code>return</code> <code>bindingCtor;</code>

<code>    </code><code>String clsName = cls.getName();</code>

<code>    </code><code>if</code> <code>(clsName.startsWith(</code><code>"android."</code><code>) || clsName.startsWith(</code><code>"java."</code><code>)) {</code>

<code>      </code><code>if</code> <code>(debug) Log.d(TAG, </code><code>"MISS: Reached framework class. Abandoning search."</code><code>);</code>

<code>      </code><code>return</code> <code>null</code><code>;</code>

<code>        </code><code>Log.d(</code><code>"Sandy"</code><code>, </code><code>"findBindingConsForClass: "</code> <code>+ clsName + </code><code>" vindBinding name: "</code> <code>+</code>

<code>                </code><code>clsName + </code><code>"_ViewBinding"</code><code>);</code>

<code>      </code><code>Class&lt;?&gt; bindingClass = cls.getClassLoader().loadClass(clsName + </code><code>"_ViewBinding"</code><code>);</code>

<code>      </code><code>//noinspection unchecked</code>

<code>      </code><code>bindingCtor = (Constructor&lt;? </code><code>extends</code> <code>Unbinder&gt;) bindingClass.getConstructor(cls, View.</code><code>class</code><code>);</code>

<code>      </code><code>if</code> <code>(debug) Log.d(TAG, </code><code>"HIT: Loaded binding class and constructor."</code><code>);</code>

<code>    </code><code>} </code><code>catch</code> <code>(ClassNotFoundException e) {</code>

<code>      </code><code>if</code> <code>(debug) Log.d(TAG, </code><code>"Not found. Trying superclass "</code> <code>+ cls.getSuperclass().getName());</code>

<code>      </code><code>bindingCtor = findBindingConstructorForClass(cls.getSuperclass());</code>

<code>    </code><code>} </code><code>catch</code> <code>(NoSuchMethodException e) {</code>

<code>      </code><code>throw</code> <code>new</code> <code>RuntimeException(</code><code>"Unable to find binding constructor for "</code> <code>+ clsName, e);</code>

<code>    </code><code>BINDINGS.put(cls, bindingCtor);</code>

<code>    </code><code>return</code> <code>bindingCtor;</code>

上面這段代碼有幾個注意點:

a. sourceView代表是DecorView,也就是我們視窗的頂級View。

b. findBindingConstructorForClass有個BINDINGS緩存,key是class,value是緩存的Unbinder對象,這樣做可以加快bind速度。

因為每個類的ButterKnife注解在運作期間是不會變的,比如MainActivity有3個ButterKnife注解,那麼它就是3個。除非有新的apk安裝。

是以适合用緩存來實作。

c. findBindingConstructorForClass使用了遞歸的方法

這個方法使用了遞歸,不斷調用父類,也就是

<code>catch</code> <code>(ClassNotFoundException e) {</code>

那為什麼要這麼處理呢?

因為有些Activity沒有ButterKnife的注解,但是它的父類可能有,比如BaseActivity。是以需要往上遞歸。那什麼時候遞歸結束呢?

<code>if</code> <code>(bindingCtor != </code><code>null</code><code>) {</code>

如果緩存裡面找到了結果,那麼結束,同時傳回結果;

或者類名以"android."或者"java."開頭,也結束,傳回null;

以Activity為例,Activity的類名是android.app.Activity,是以你的MainActivity如果遞歸到Activity還沒有找到ButterKnife注解,那就說明你的MainActivity是沒有包含ButterKnife注解的。

d. 如果子Activity和父Activity都有ButterKnife注解怎麼辦?

答案是傳回子Activity以及其對應的 Constructor&lt;? extends Unbinder&gt; bindingCtor對象

那它的父Activity如果也有ButterKnife注解怎麼辦?怎麼解析父Activity的ButterKnife注解呢? 這個問題我們待會再講。

記為問題1。

e. Constructor&lt;? extends Unbinder&gt; 是個什麼東西?

調用ButterKnife.bind(Activity target)方法後會傳回一個Unbinder對象,可以在onDestroy中調用unbind()方法,那個Unbinder是什麼東西呢?這個問題待會再講。

記為問題2.

f. clsName + "_ViewBinding"是什麼類?

Class&lt;?&gt; bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

這個問題記為問題3.

2. ButterKnifeProcessor.java

這個類是ButterKnife裡面很重要的一個類了,它繼承自AbstractProcessor。

看來不懂的問題越來越多,那麼有必要來學習下Java注解的知識。

八. Java注解

在分析ButterKnife代碼前,需要了解Java的注解,需要了解Annotation Processor,因為ButterKnifer用到這個知識。

這裡面很重要的一個知識點就是你可以編寫一定的規則,讓它在應用程式編譯時執行你的規則,然後生成Java代碼;并且生成的Java還可以參與編譯。

<a href="https://race604.com/annotation-processing/" target="_blank">Java注解</a>

九. 自己定義的注解架構

1. Eclipse實作

主要是參考這篇文章完成的,大家可以參考這篇文章:

<a href="http://blog.csdn.net/lmj623565791/article/details/43452969" target="_blank">Eclipse中使用Java注解Processor</a>

主要說下不同的地方:

a. source folder的建立,直接File-&gt;New-&gt;source folder一直建立不成功,後面用另外一種方法建立成功了。

項目-&gt;右擊-&gt;Properties-&gt;Java Build Path-&gt;Source-&gt;Add Folder-&gt;Create New Folder-&gt;輸入resources/META-INF/services-&gt;finish-&gt;ok-&gt;ok

<a href="https://s1.51cto.com/wyfs02/M00/95/55/wKiom1kUG_CSP3OvAACTzaqjj0w123.png-wh_500x0-wm_3-wmp_4-s_4260205487.png" target="_blank"></a>

2. Android studio實作

<a href="http://blog.csdn.net/a1018875550/article/details/52166916" target="_blank">AndroidStudio下面使用Java注解Processor</a>

十. 調試自己的自定義架構

有個時候需要調試自己寫的架構是否正常運作,下面介紹下調試:

1. 在項目gradle.properties裡面添加

<code>org.gradle.daemon=true</code>

<code>org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8011</code>

2.Edit Configureations

<a href="https://s3.51cto.com/wyfs02/M02/95/5D/wKioL1kUUgXzetkqAADJBDdHWVg272.png-wh_500x0-wm_3-wmp_4-s_189248235.png" target="_blank"></a>

3. 增加遠端調試

<a href="https://s5.51cto.com/wyfs02/M00/95/5D/wKiom1kUUlWiJR1iAADIV1Xx2G0912.png-wh_500x0-wm_3-wmp_4-s_1570904271.png" target="_blank"></a>

4. 啟動遠端調試

<a href="https://s4.51cto.com/wyfs02/M01/95/5D/wKioL1kUUqSykGMqAAAtwfDm2S0203.png-wh_500x0-wm_3-wmp_4-s_1078494978.png" target="_blank"></a>

下面的控制台會出現下面的提示:

Connected to the target VM, address: 'localhost:8011', transport: 'socket'

5. 打斷點

在Processor裡面打上斷點,比如init, process

6. 連上手機,項目根目錄指令行下執行

gradle clean connectedCheck

十一. ButterKnife使用Java注解

了解了Java注解Processor之後,ButterKnife就比較好了解了。

首先它的ButterKnifeProcessor.java繼承自AbstractProcessor,重寫了init和process之類的方法。

也就是說它在編譯的時候會被執行,生成輔助代碼。

它的輔助代碼生成到哪裡了呢?

在我們自己的應用程式裡面搜尋_ViewBinding,就可以找到已經生成好的輔助類,如下:

<code>public</code> <code>class</code> <code>xxxx_ViewBinding&lt;T </code><code>extends</code> <code>LoginCloudActivity&gt; </code><code>extends</code> <code>BaseActivity_ViewBinding&lt;T&gt; {</code>

<code>  </code><code>private</code> <code>View view2131689593;</code>

<code>  </code><code>@UiThread</code>

<code>  </code><code>public</code> <code>xxxx_ViewBinding(</code><code>final</code> <code>T target, View source) {</code>

<code>    </code><code>super</code><code>(target, source);</code>

<code>    </code><code>View view;</code>

<code>    </code><code>target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user, </code><code>"field 'mEtUser'"</code><code>, EditText.</code><code>class</code><code>);</code>

<code>    </code><code>target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd, </code><code>"field 'mEtPwd'"</code><code>, EditText.</code><code>class</code><code>);</code>

<code>    </code><code>view = Utils.findRequiredView(source, R.id.btn_login, </code><code>"method 'btn_login' and method 'btn_login_long'"</code><code>);</code>

<code>    </code><code>view2131689593 = view;</code>

<code>    </code><code>view.setOnClickListener(</code><code>new</code> <code>DebouncingOnClickListener() {</code>

<code>      </code><code>@Override</code>

<code>      </code><code>public</code> <code>void</code> <code>doClick(View p0) {</code>

<code>        </code><code>target.btn_login();</code>

<code>    </code><code>});</code>

<code>    </code><code>view.setOnLongClickListener(</code><code>new</code> <code>View.OnLongClickListener() {</code>

<code>      </code><code>public</code> <code>boolean</code> <code>onLongClick(View p0) {</code>

<code>        </code><code>return</code> <code>target.btn_login_long();</code>

<code>  </code><code>}</code>

<code>  </code><code>@Override</code>

<code>  </code><code>public</code> <code>void</code> <code>unbind() {</code>

<code>    </code><code>T target = </code><code>this</code><code>.target;</code>

<code>    </code><code>super</code><code>.unbind();</code>

<code>    </code><code>target.mEtUser = </code><code>null</code><code>;</code>

<code>    </code><code>target.mEtPwd = </code><code>null</code><code>;</code>

<code>    </code><code>view2131689593.setOnClickListener(</code><code>null</code><code>);</code>

<code>    </code><code>view2131689593.setOnLongClickListener(</code><code>null</code><code>);</code>

<code>    </code><code>view2131689593 = </code><code>null</code><code>;</code>

這個類在編譯的時候會被自動生成,那麼在運作的時候,它會被調用。

這個類的構造函數會去初始化那些控件,設定監聽。

回到第七步 ButterKnife.bind()的過程

在createBinding的時候,它會初始化這個xxx_ViewBinding類,如下:

<code>      </code><code>...</code>

<code>      </code> 

那麼就會走到xxx_ViewBinding的構造函數,那麼就會初始化控件,同時也會設定監聽。如下:

<code>target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user, </code><code>"field 'mEtUser'"</code><code>, EditText.</code><code>class</code><code>);</code>

它的調用方式直接是target.mEtpwd,是以也就是說Activity的mEtpwd控件不能是private的,否則會引用不到。

參考網址:

<a href="https://github.com/JakeWharton/butterknife" target="_blank">butterknife github源代碼下載下傳</a>

<a href="http://blog.csdn.net/chenkai19920410/article/details/51020151" target="_blank">ButterKnife源代碼解析</a>

<a href="https://race604.com/annotation-processing/" target="_blank">Java注解處理器分析</a>

<a href="http://blog.csdn.net/lmj623565791/article/details/43452969" target="_blank">Eclipse中使用Java注解處理器</a>

<a href="http://blog.csdn.net/a1018875550/article/details/52166916" target="_blank">Android studio使用java注解處理器</a>

<a href="http://www.jianshu.com/p/95f12f72f69a" target="_blank">JavaPoet介紹</a>

<a href="http://blog.csdn.net/pizza_lawson/article/details/52126325" target="_blank">調試Java注解處理器出錯</a>

     本文轉自rongwei84n 51CTO部落格,原文連結:http://blog.51cto.com/483181/1924089,如需轉載請自行聯系原作者

繼續閱讀