一. ButterKnife介紹
創(chuàng)新互聯(lián)網(wǎng)絡公司擁有10余年的成都網(wǎng)站開發(fā)建設(shè)經(jīng)驗,近千家客戶的共同信賴。提供成都網(wǎng)站設(shè)計、成都做網(wǎng)站、網(wǎng)站開發(fā)、網(wǎng)站定制、賣友情鏈接、建網(wǎng)站、網(wǎng)站搭建、成都響應式網(wǎng)站建設(shè)公司、網(wǎng)頁設(shè)計師打造企業(yè)風格,提供周到的售前咨詢和貼心的售后服務
在Android編程過程中,我們會寫大量的布局和點擊事件,像初始view、設(shè)置view監(jiān)聽這樣簡單而重復的操作,這些代碼繁瑣而又不雅觀,比如:
TextView tvSetName = findViewById(R.id.xxx); tvSetName.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //xxxx } }); TextView tvSetAge = findViewById(R.id.xxx); tvSetAge.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //xxxx } }); TextView tvSetArea = findViewById(R.id.xxx); tvSetArea.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //xxxx } });
Activity中這種代碼多了之后,很不雅觀。
二. 使用簡介
ButterKnife使用方法比較簡單,主要包括以下步驟:
引用ButterKnife包
在onCreate里面bind(setContentView之后)
綁定各種事件
onDestroy里面解綁釋放資源
ButterKnife使用方法
三. ButterKnife源代碼下載
ButterKnife github源代碼地址
直接git clone或者下載zip即可。
四. 編譯
Android studio打開ButterKnife源代碼
AndroidStudio->File->open->ButterKnife源代碼路徑->確認
Build->Rebuild Project
五. 生成的aar和jar包
生成的包主要有兩個
butterknife-annotations-8.5.2-SNAPSHOT.jar
路徑:butterknife-annotations->build->libs
butterknife-release.aar
路徑: butterknife->build->outputs->aar
六. 其他應用引用自定義ButterKnife包
刪除原來ButterKnife包引用,因為要使用自己編譯的包
// compile 'com.jakewharton:butterknife:8.4.0'
拷貝文件
拷貝上面兩個文件到自己項目app模塊的libs 目錄
添加aar的關(guān)聯(lián)
打開app模塊的build.gradle文件,添加:
compile fileTree(include: ['*.jar'], dir: 'libs') compile(name: 'butterknife-release', ext: 'aar')
Build->Rebuild Project
七. 源代碼分析
1. ButterKnife.bind(Activity target)過程
文件名:ButterKnife.java
static final Map, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>(); public static Unbinder bind(@NonNull Activity target) { Log.d("Sandy", "ButterKnife bind.. target: " + target); View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); } private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class> targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); } } private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) { Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { Log.d("Sandy", "findBindingConsForClass: " + clsName + " vindBinding name: " + clsName + "_ViewBinding"); Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); //noinspection unchecked bindingCtor = (Constructor extends Unbinder>) bindingClass.getConstructor(cls, View.class); if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor."); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } BINDINGS.put(cls, bindingCtor); return bindingCtor; }
上面這段代碼有幾個注意點:
a. sourceView代表是DecorView,也就是我們窗口的頂級View。
b. findBindingConstructorForClass有個BINDINGS緩存,key是class,value是緩存的Unbinder對象,這樣做可以加快bind速度。
因為每個類的ButterKnife注解在運行期間是不會變的,比如MainActivity有3個ButterKnife注解,那么它就是3個。除非有新的apk安裝。
所以適合用緩存來實現(xiàn)。
c. findBindingConstructorForClass使用了遞歸的方法
這個方法使用了遞歸,不斷調(diào)用父類,也就是
catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); }
那為什么要這么處理呢?
因為有些Activity沒有ButterKnife的注解,但是它的父類可能有,比如BaseActivity。所以需要往上遞歸。那什么時候遞歸結(jié)束呢?
if (bindingCtor != null) { if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; }
如果緩存里面找到了結(jié)果,那么結(jié)束,同時返回結(jié)果;
或者類名以"android."或者"java."開頭,也結(jié)束,返回null;
以Activity為例,Activity的類名是android.app.Activity,所以你的MainActivity如果遞歸到Activity還沒有找到ButterKnife注解,那就說明你的MainActivity是沒有包含ButterKnife注解的。
d. 如果子Activity和父Activity都有ButterKnife注解怎么辦?
答案是返回子Activity以及其對應的 Constructor extends Unbinder> bindingCtor對象
那它的父Activity如果也有ButterKnife注解怎么辦?怎么解析父Activity的ButterKnife注解呢? 這個問題我們待會再講。
記為問題1。
e. Constructor extends Unbinder> 是個什么東西?
調(diào)用ButterKnife.bind(Activity target)方法后會返回一個Unbinder對象,可以在onDestroy中調(diào)用unbind()方法,那個Unbinder是什么東西呢?這個問題待會再講。
記為問題2.
f. clsName + "_ViewBinding"是什么類?
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
這個問題記為問題3.
2. ButterKnifeProcessor.java
這個類是ButterKnife里面很重要的一個類了,它繼承自AbstractProcessor。
看來不懂的問題越來越多,那么有必要來學習下Java注解的知識。
八. Java注解
在分析ButterKnife代碼前,需要了解Java的注解,需要了解Annotation Processor,因為ButterKnifer用到這個知識。
這里面很重要的一個知識點就是你可以編寫一定的規(guī)則,讓它在應用程序編譯時執(zhí)行你的規(guī)則,然后生成Java代碼;并且生成的Java還可以參與編譯。
Java注解
九. 自己定義的注解框架
1. Eclipse實現(xiàn)
主要是參考這篇帖子完成的,大家可以參考這篇帖子:
Eclipse中使用Java注解Processor
主要說下不同的地方:
a. source folder的創(chuàng)建,直接File->New->source folder一直創(chuàng)建不成功,后面用另外一種方法創(chuàng)建成功了。
項目->右擊->Properties->Java Build Path->Source->Add Folder->Create New Folder->輸入resources/META-INF/services->finish->ok->ok
2. Android studio實現(xiàn)
AndroidStudio下面使用Java注解Processor
十. 調(diào)試自己的自定義框架
有個時候需要調(diào)試自己寫的框架是否正常運行,下面介紹下調(diào)試:
1. 在項目gradle.properties里面添加
org.gradle.daemon=true org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8011
2.Edit Configureations
3. 增加遠程調(diào)試
4. 啟動遠程調(diào)試
下面的控制臺會出現(xiàn)下面的提示:
Connected to the target VM, address: 'localhost:8011', transport: 'socket'
5. 打斷點
在Processor里面打上斷點,比如init, process
6. 連上手機,項目根目錄命令行下執(zhí)行
gradle clean connectedCheck
十一. ButterKnife使用Java注解
理解了Java注解Processor之后,ButterKnife就比較好理解了。
首先它的ButterKnifeProcessor.java繼承自AbstractProcessor,重寫了init和process之類的方法。
也就是說它在編譯的時候會被執(zhí)行,生成輔助代碼。
它的輔助代碼生成到哪里了呢?
在我們自己的應用程序里面搜索_ViewBinding,就可以找到已經(jīng)生成好的輔助類,如下:
public class xxxx_ViewBindingextends BaseActivity_ViewBinding { private View view2131689593; @UiThread public xxxx_ViewBinding(final T target, View source) { super(target, source); View view; target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user, "field 'mEtUser'", EditText.class); target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd, "field 'mEtPwd'", EditText.class); view = Utils.findRequiredView(source, R.id.btn_login, "method 'btn_login' and method 'btn_login_long'"); view2131689593 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.btn_login(); } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View p0) { return target.btn_login_long(); } }); } @Override public void unbind() { T target = this.target; super.unbind(); target.mEtUser = null; target.mEtPwd = null; view2131689593.setOnClickListener(null); view2131689593.setOnLongClickListener(null); view2131689593 = null; } }
這個類在編譯的時候會被自動生成,那么在運行的時候,它會被調(diào)用。
這個類的構(gòu)造函數(shù)會去初始化那些控件,設(shè)置監(jiān)聽。
回到第七步 ButterKnife.bind()的過程
在createBinding的時候,它會初始化這個xxx_ViewBinding類,如下:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class> targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); ... }
那么就會走到xxx_ViewBinding的構(gòu)造函數(shù),那么就會初始化控件,同時也會設(shè)置監(jiān)聽。如下:
target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user, "field 'mEtUser'", EditText.class); target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd, "field 'mEtPwd'", EditText.class); view = Utils.findRequiredView(source, R.id.btn_login, "method 'btn_login' and method 'btn_login_long'"); view2131689593 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.btn_login(); } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View p0) { return target.btn_login_long(); } });
它的調(diào)用方式直接是target.mEtpwd,所以也就是說Activity的mEtpwd控件不能是private的,否則會引用不到。
參考網(wǎng)址:
butterknife github源代碼下載
ButterKnife源代碼解析
Java注解處理器分析
Eclipse中使用Java注解處理器
Android studio使用java注解處理器
JavaPoet介紹
調(diào)試Java注解處理器出錯