ButterKnife GitHub 2019 年已經(jīng)更新到 10.1.0
版本了沪停,經(jīng)過幾天的學(xué)習(xí)和網(wǎng)上搜索資料,發(fā)現(xiàn)之前版本的一些疑難雜癥已經(jīng)不存在或者有些改善裳涛。剛開始用是非常爽的木张,如果真的要應(yīng)用在項(xiàng)目中,有如下建議:
- 一個(gè) module 擼到底的項(xiàng)目端三,直接用吧舷礼,沒啥坑,都挺好郊闯。
- 大項(xiàng)目組件化的項(xiàng)目妻献,可以嘗試。低版本據(jù)搜索有很多坑团赁,我在 10.1.0 版本實(shí)驗(yàn)了一下育拨,配置得當(dāng)沒有問題。但我這個(gè)組件化寫得很簡(jiǎn)單欢摄,如果你的項(xiàng)目更加復(fù)雜熬丧,就要?jiǎng)邮衷囋嚵耍枰噧蓚€(gè)地方:一個(gè)看能不能編譯通過剧浸;另一個(gè)看運(yùn)行期間綁定的是否正確锹引。
配置步驟
這個(gè)步驟按照 github 頁(yè)面上的說明設(shè)置就行,這里用列表記錄一下步驟:
給 application 模塊配置:
- 設(shè)置為 Java8唆香,介個(gè)
10.1.0
船新版本得用 8 了嫌变。
android {
...
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
- 添加依賴:庫(kù) + 注解處理
dependencies {
implementation 'com.jakewharton:butterknife:10.1.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
給 library 模塊設(shè)置:
- 先按照 application 模塊的方法設(shè)置一遍
- 在 project level 的 gradle 文件中添加 buildscript 依賴
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.0'
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0' // 這里
}
}
- 在 library 模塊的 gradle 文件中 apply,寫在 android library 插件的下面
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife' // 這里
基本使用
舉個(gè)例子
@BindView(R.id.clock_view) // 找到資源
View mClockView; // 找到變量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lib_main);
ButterKnife.bind(this); // 綁定
}
ButterKnife 的主要目的是簡(jiǎn)化資源和代碼之間的綁定躬它,使用起來(lái)很簡(jiǎn)單腾啥,主要有以下(看起來(lái)像廢話一樣的)幾個(gè)步驟:
- 找到資源:
R.id.clock_view
- 如果是 application 模塊,通過
R.id.xxx
,R.string.xxx
等找到資源冯吓。 - 如果是 library 模塊倘待,需要使用 ButterKnife 生成的
R2
來(lái)替代R
。原因如下:
- 如果是 application 模塊,通過
因?yàn)樽⒔庵械?
ElementValue
值(寫在括號(hào)內(nèi)的R.id.xxx
)必須是常量组贺,而R
在 library 中的 id 值都是變量凸舵。為什么注解中的
ElementValue
必須是常量?因?yàn)槭褂米⒔鈺r(shí)產(chǎn)生的所有信息在編譯期間必須確定下來(lái)失尖,直接寫入注解內(nèi)部的數(shù)據(jù)結(jié)構(gòu)中啊奄。即使是 @Retention(RetentionPolicy.RUNTIME) 修飾的注解渐苏,也不可能在運(yùn)行時(shí)動(dòng)態(tài)運(yùn)行一段字節(jié)碼來(lái)計(jì)算,這樣會(huì)徒增復(fù)雜度而且沒有什么好處菇夸。在 application 模塊中琼富,
R
類中的標(biāo)識(shí)符都是 final 的,也就是常量庄新,是在編譯期就能確定值的鞠眉。而在 library 模塊中,R
類中的標(biāo)識(shí)符不是 final 的择诈,在編譯期無(wú)法確定械蹋。library 中不用常量的理由是R
中的各種 id 值在一個(gè) app 內(nèi)必須是互不相同的,如果 library 模塊在編譯期就將這些 id 確定為常量的話吭从,那么必須要考慮所有編譯模塊朝蜘,會(huì)降低編譯速度;而且 library 模塊是共享的涩金,如果使用了固定的 id 值谱醇,分發(fā)給其他項(xiàng)目使用難免會(huì)產(chǎn)生沖突。具體分析可以參考這個(gè)官方頁(yè)面:Non-constant Fields in Case Labels
- 找到變量:
View mClockView;
- 不能聲明成 private步做,因?yàn)橐ㄟ^ ButterKnife 生成的類訪問副渴,而 private 修飾的成員只有自己才能訪問。ButterKnife 生成的類與綁定的類在同一個(gè)包內(nèi)全度,直接什么都不寫用包訪問權(quán)限就可以了煮剧。
- 一旦寫好變量和修飾它的注解,ButterKnife 就可以生成綁定的代碼了将鸵,注意只是生成了綁定的代碼勉盅,如果不調(diào)用這個(gè)生成的代碼,也是沒有完成綁定的顶掉。綁定的代碼很簡(jiǎn)單草娜,就是將找到的資源和變量關(guān)聯(lián)起來(lái)。
- 綁定:
ButterKnife.bind(this);
這個(gè)步驟就是調(diào)用 ButterKnife 生成的綁定代碼痒筒。在調(diào)用綁定代碼之前宰闰,應(yīng)該設(shè)置好 layout 文件,以便能通過資源 id 找到資源簿透;在調(diào)用綁定代碼之后移袍,成員變量才綁定到資源上,這時(shí)才能訪問成員變量老充。
-
根據(jù)綁定變量所在的類的類型葡盗,bind 方法還有幾個(gè)版本,比如綁定 Fragment 中的變量要用
bind(this, view)
啡浊,完整代碼:@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_first, container, false); unbinder = ButterKnife.bind(this, view); return view; }
bind 方法的參數(shù) target 必須是綁定的成員所在的類的對(duì)象戳粒。
bind 方法會(huì)返回一個(gè) Unbinder 對(duì)象路狮,可以用來(lái)解除綁定。但幾乎所有情況都不需要手動(dòng)調(diào)用蔚约,只有在 Fragment 中綁定才需要在
onDestroyView()
中手動(dòng)解除。
原理簡(jiǎn)要分析
說白了 ButterKnife 就是替我們寫了一些重復(fù)度很高的代碼涂籽,我們只用關(guān)注綁定關(guān)系本身苹祟,重復(fù)的綁定代碼都由 ButterKnife 生成。
那么憑什么 ButterKnife 能替我們寫代碼呢评雌,就要借助注解(Annotation)和注解處理工具(APT)了树枫。
簡(jiǎn)單說一下注解,注解就是帶 @
符號(hào)開頭的修飾類景东、方法砂轻、變量等等的一些看起來(lái)不像代碼的東西。Java 語(yǔ)言本身有一些內(nèi)置的注解斤吐,最常見的要數(shù) @Override
了搔涝。可以把注解當(dāng)做給 Java 中的類和措、方法庄呈、字段等語(yǔ)法元素添加屬性,再通過各種工具進(jìn)行處理派阱,來(lái)達(dá)到一定的目的诬留。對(duì) ButterKnife 來(lái)說,目的就是將資源和變量關(guān)聯(lián)起來(lái)贫母,不用再手動(dòng)調(diào)用綁定的代碼文兑。ButterKnife 利用注解給成員賦予了資源 id 的屬性,再經(jīng)過 ButterKnife 的處理就可以將兩者關(guān)聯(lián)在一起腺劣。
那么是怎么關(guān)聯(lián)的呢绿贞?這就要提一下注解的兩種主要使用形式:反射和生成代碼。ButterKnife 早期的版本就是用的反射來(lái)實(shí)現(xiàn)綁定誓酒,后來(lái)發(fā)現(xiàn)效率不如生成代碼的實(shí)現(xiàn)方式樟蠕,于是就改成了生成代碼的方式。
生成代碼的過程使用了注解處理工具(APT)靠柑,它是一個(gè)運(yùn)行在構(gòu)建流程中的一個(gè)工具寨辩,可以讀取到注解和被注解元素的信息,再通過自定義的處理器(也就是 gradle 依賴中的annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
)輸出 Java 源代碼歼冰,并將其納入構(gòu)建過程靡狞。
接下來(lái)從代碼角度分析原理:
- 對(duì)于綁定的成員所在的類,ButterKnife 會(huì)生成一個(gè)對(duì)應(yīng)的 Unbinder 類型隔嫡,也就是實(shí)現(xiàn)了 Unbinder 接口的一個(gè)類甸怕。
- 別看它叫 Unbinder甘穿,實(shí)際上綁定和解綁都是使用這個(gè)對(duì)象,綁定用構(gòu)造方法梢杭,解綁用 unbind() 方法温兼。
- 這個(gè)類與被綁定的類在同一個(gè)包下,因此可以訪問到包訪問權(quán)限的成員武契。
- 這個(gè)類的名字是按照規(guī)則生成的募判,被綁定類的名字加一個(gè)固定后綴就是這個(gè) Unbinder 類型的名字。
- ButterKnife.bind(target, view) 方法內(nèi)部會(huì)根據(jù)傳入的 target 類型咒唆,通過名稱規(guī)則拼接出生成的 Unbinder 類型的類名稱届垫,然后使用反射調(diào)用構(gòu)造方法,也就是執(zhí)行了綁定的代碼全释。再將這個(gè) Unbinder 對(duì)象返回装处,這樣就可以通過這個(gè)對(duì)象調(diào)用 unbind() 方法來(lái)解除綁定。
組件化的影響
先說明一下我這個(gè)簡(jiǎn)單的組件化是怎么做的:
- 在 gradle.properties 文件中定義了一個(gè)變量
isModule_libdemo1=true
用來(lái)設(shè)置作為 library 還是作為 application浸船。 - 在 library 模塊的 build.grale 中妄迁,通過
isModule_libdemo1
的值來(lái)使用不同 plugin。
if (isModule_libdemo1.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
- 使用不同的 manifest 文件(略)
ButterKnife 在組件化過程中有什么坑嗎糟袁?
由于是 library 模塊判族,代碼用的都是 R2
對(duì)象,一旦切換 plugin项戴,由 library 變成 application形帮,就沒有 R2
對(duì)象了嗎,就應(yīng)該用 R
對(duì)象了嗎周叮?
if (isModule_libdemo1.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
apply plugin: 'com.jakewharton.butterknife'
即使是 application辩撑,也可以使用 ButterKnife 插件,可以生成 R2
對(duì)象仿耽,因此應(yīng)該統(tǒng)一使用 R2
來(lái)尋找資源合冀。
其他注意事項(xiàng)
- 重構(gòu)資源名稱:用重構(gòu)工具可以幫你快速修改。但 ButterKnife 生成的
R2
中的名字 Android Studio 可不管项贺,這時(shí)編譯一下就能發(fā)現(xiàn)問題君躺。但最好不要改成原來(lái)就有的名字,會(huì)沒有任何提示默默編譯成功开缎,在運(yùn)行的時(shí)候給你出錯(cuò)棕叫。 - @OnClick 與
R2
:在 @OnClick(id) 修飾的方法中,即使 id 是R2
的寫法奕删,方法體內(nèi)部仍要使用R
對(duì)象中的 id 來(lái)區(qū)分多個(gè)按鈕的 id俺泣。可以這么理解,R2
是 ButterKnife 為了繞開資源 id 非常量的問題伏钠,R2
與R
是能一一對(duì)應(yīng)的横漏,因此能夠在生成的 Unbinder 代碼使用正確的R
中的 id。
(ole)