[TOC]
# ButterKnife源碼分析
> 基于 ButterKnife 8.8.0版本進行分析
## ButterKnife 使用
### 依賴說明
```
dependencies {
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
```
### 簡單使用
```java
class ExampleActivity extends Activity {
@BindView(R.id.user) EditText username;
@BindView(R.id.pass) EditText password;
@BindString(R.string.login_error) String loginErrorMessage;
@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...
}
}
```
### 多模塊使用
> 在Library 中使用ButterKnife 以上操作是無法使用的 ,我們還需要借助`Butterknife插件`來實現功能
在您的**root build.gradle**文件中buildscript:
```
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
}
}
```
#### 添加插件 apply
```
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
```
#### 插件使用Butterknife
```
class ExampleActivity extends Activity {
@BindView(R2.id.user) EditText username;
@BindView(R2.id.pass) EditText password;
...
}
```
### 問題說明
* 在Library中無法使用在注解中無法使用R的資源
> 原因很簡單,因為Lib模塊中 java注解無法使用變量 然而lib生成的R 文件資源都是 `public static` 不是 `public static final ` 簡單的說lib中生成的R文件不是常量
編譯器將會報錯:Attribute value must be constant ,如下圖:

[Butterknife 官網簡單使用說明](http://jakewharton.github.io/butterknife/)
## ButterKnife源碼分析
### [源碼地址](https://github.com/JakeWharton/butterknife)
### 源碼module結構
#### 組件依賴關系
> ButterKnife 共7個組件,他們的依賴關系如下圖所示 butterknife-integration-test;該項目的測試用例--不做介紹

+ butterknife:這個工程提供了 ButterKnife.bind(this),這是 ButterKnife 對外提供的門面。也是運行時,觸發 Activity 中 View 控件綁定的時機,提供android使用的API。
+ butterknife-compiler:java-model 編譯期間將使用該工程,他的作用是解析注解,并且生成 Activity 中 View 綁定的 Java 文件。
+ butterknife-annotations:java-model 將所有自定義的注解放在此工程下, 確保職責的單一。
+ butterknife-gradle-plugin:gradle 插件,這是8.2.0版本起為了支持 library 工程而新增的一個插件工程。
+ butterknife-lint:針對 butterknife-gradle-plugin 而做的靜態代碼檢查工具,非常有態度的一種做法,在下文做詳細介紹。
### 編譯 生成Java代碼
> 前提以上基本用法已經加入工程
##### 簡單使用
```java
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
Button btn;
@BindView(R.id.tv1)
TextView tv;
@OnClick(R.id.btn1)
public void btnClick() {
Toast.makeText(this, "Butterknfie 簡單使用", Toast.LENGTH_SHORT).show();
}
}
```
#### Butterknife 之 編譯期
> android-apt(Annotation Processing Tool) ,在Java代碼的編譯時期,javac 會調用java注解處理器來生成輔助代碼。生成的代碼就在 `build/generated/source/apt`
```java
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131427416;
@UiThread
public MainActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.btn1, "field 'btn' and method 'btnClick'");
//這里的 target 其實就是我們的 Activity
//這個castView就是將得到的View轉化成具體的子View
target.btn = Utils.castView(view, R.id.btn1, "field 'btn'", Button.class);
view2131427416 = view;
//為按鈕設置點擊事件
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.btnClick();
}
});
target.tv = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.btn = null;
target.tv = null;
view2131427416.setOnClickListener(null);
view2131427416 = null;
this.target = null;
}
```
* Utils.findRequiredView 方法的封裝
```java
// Utils.findRequiredView
View view = source.findViewById(id);
if (view != null) {
return view;
}
```
### Butterknife 之 android-apt(Annotation Processing Tool)
> APT(Annotation Processing Tool),即注解處理工具。在該方案中,通常有個必備的三件套,分別是注解處理器 Processor,注冊注解處理器 AutoService 和代碼生成工具 JavaPoet。
#### 注解處理器 Processor
> ButterKnife 一切皆注解,因此首先需要個處理器來解析注解。 ButterKnifeProcessor 充當了該角色,其中 process 方法是觸發注解解析的入口,所有的神奇的事情從這里發生。
process 方法中主要做兩件事情,分別是:
+ 解析所有包含了 ButterKnife 注解的類
+ 根據解析結果,使用 JavaPoet 生成相應的Java文件

`findAndParseTargets(env)` 中解析注解的代碼非常冗長,依次對 `@BindArray` 、`@BindColor`、`@BindString`、`@BindView` 等注解進行解析,解析結果存放在 bindingMap 中。
這里重點關注下 bindingMap 的鍵值對。key 值為 TypeElement 對象 ,可以簡單的理解為被解析的類本身,而 value 值為 BindingSet 對象,該對象存放了解析結果,根據該結果,JavaPoet 將生成不同的 Java 文件,
以官方 sample 為例,其映射關系如下:
| key | value |JavaPoet 根據 value 生成的文件 |
| ------| ----- | ------|
|SimpleActivity |BindingSet |SimpleActivity_ViewBinding.java
|SimpleAdapter |BindingSet |SimpleAdapter$ViewHolder_ViewBinding.java

#### 注冊注解處理器 [AutoService](https://link.jianshu.com/?t=https://github.com/google/auto/tree/master/service)
> 定義完注解處理器后,還需要告訴編譯器該注解處理器的信息,需在 `src/main/resource/META-INF/service` 目錄下增加 `javax.annotation.processing.Processor` 文件,并將注解處理器的類名配置在該文件中。
整個過程比較繁瑣,Google 為我們提供了更便利的工具,叫 `AutoService`,此時只需要為注解處理器增加 `@AutoService` 注解就可以了,如下:
```java
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
}
```
[注] [AutoService使用的是`java.util.ServiceLoader`]
#### Java編寫器 [JavaPoet](https://github.com/square/javapoet)
> 了解 JavaPoet ,最好的方式便是看[官方文檔](https://github.com/square/javapoet)。簡而言之,當我們寫一個類時,其實是有固定結構的,JavaPoet 提供了生成這些結構的 api
舉例如下:
+ 類:TypeSpec.classBuilder()
+ 構造器:MethodSpec.constructorBuilder()
+ 方法:MethodSpec.methodBuilder()
+ 參數:ParameterSpec.builder()
+ 屬性:FieldSpec.builder()
+ 程序片段:CodeBlock.builder()
以 ButterKnife 而言,他做的事情便是將注解處理器解析后的結果(實際上就是上文提到的 BindingSet 對象)生成 Activity_ViewBinding.java,該對象負責綁定 Activity 中的 View 控件以及設置監聽器等。
那么 JavaPoet 是如何處理的?實際上 ButterKnife 會將上文提到的 BindingSet 轉換成類似于下文所示的代碼:
示例如下:
```java
// 創建類
TypeSpec typeSpec = TypeSpec.classBuilder("TestActivity_ViewBinding")
.addModifiers(PUBLIC) // 類為public
.addSuperinterface(UNBINDER) // 類為Unbinder的實現類
.addField(targetField) // 生成屬性 private TestActivity target
.addMethod(constructorForActivity) // 生成構造器1
.addMethod(otherConstructor) // 生成構造器2
.addMethod(unBindeMethod) // 生成unbind()方法
.build();
// 生成 Java 文件
JavaFile javaFile = JavaFile.builder("com.zdg", typeSpec)//包名和類
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
javaFile.writeTo(System.out);
```
最后總結下這三件套的協作流程,如下圖:

### Butterknife 之 運行期
> 接下來我們來分析下運行期間發生的事情,相比于編譯期間,運行期間的邏輯簡單了許多。繼續使用上面的Demo例子
運行時的入口在于 `ButterKnife.bind(this)`,追溯源碼發現,最終將會執行以下邏輯:
```java
// 最終將找到 SimpleActivity_ViewBinding 的構造器,并實例化
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
constructor.newInstance(target, source);
```
也就是說 ButterKnife.bind(this) 等價于如下代碼:
```java
View sourceView = activity.getWindow().getDecorView();
new SimpleActivity_ViewBinding(activity,sourceView);
```
注:雖然這里使用了反射,但源碼中將 `Class.forName` 的結果緩存起來后再通過 `newInstance` 創建實例,避免重復加載類,提升性能。
編譯期間和運行期間相輔相成,這便是 android-apt 的普遍套路。
### Library
> 編譯時和運行時的問題解決了,還有最后一個問題:由 R 生成 R2 的意義是什么?
如果你細心的話會發現在官方的 sample-library 中,注解的值均是由 R2 來引用的,如下圖:

如果非 library 工程,則仍然引用系統生成的 R 文件。所以可以猜測:R2 的誕生是為 library 工程量身打造的。
`在上面我說過R文件的問題,library中生成的R文件資源文件不是常量 無法使用注解`
`JakeWharton大神`他是怎么解決這個問題呢???
> 既然 R 不能滿足要求,那就自己構建一個 R2,由 R 復制而來,并且將其屬性都修改為 public static final 來修飾的常量。為了讓使用者對整個過程無感知,因此使用 gradle 插件來解決這個需求,這也是 `butterknife-gradle-plugin` 工程的由來。
#### butterknife-gradle-plugin
`butterknife-gradle-plugin` 有兩個重要的第三方依賴,分別是 `javaparser` 和 `javapoet` ,前者用于解析 Java 文件,也就是解析 R 文件,后者用于將解析結果生成 R2 文件。
整個插件工程的源碼并不難理解,在生成 R2 文件時,要將屬性定義成 public static final ,在源碼中我們可以看到此邏輯,在 FinalRClassBuilder.addResourceField() 中 :
```java
FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(int.class, fieldName)
.addModifiers(PUBLIC, STATIC, FINAL)
.initializer(fieldValue);
```
`butterknife` 插件在 `processResources` 的 Task 中執行,該任務通常用來完成文件的 copy。
有關插件的編寫 大家可以查看其他插件編寫教程
#### JakeWharton 給ButterKnife 的情懷和態度------butterknife-lint
>一個靜態代碼檢查工具,用來驗證非法的 R2 引用。一旦在我們的業務項目里不小心引用了 R2 文件,
當執行 Lint 后,將會有如下圖的提示信息:

[參考文章](https://www.jianshu.com/p/b8b59fb80554)
- 計算機基礎
- 簡答1
- 簡答2
- 專案
- 淺談0與1
- 淺談TCP_IP
- 淺談HTTP
- 淺談HTTPS
- 數據結構與算法
- 常見數據結構簡介
- 常用算法分析
- 常見排序算法
- Java數據結構類問題簡答
- 專案
- HashMap
- 淺談二叉樹
- 算法題
- 算法001_TopN問題
- 算法002_漢諾塔
- 編程思想
- 雜說
- 觀點_優秀程序設計的18大原則
- 設計模式_創建型
- 1_
- 2_
- 設計模式_結構型
- 1_
- 2_
- 設計模式_行為型
- 1_
- 2_
- Java相關
- 簡答1
- 簡答2
- 專案
- 淺談String
- 淺談Java泛型
- 淺談Java異常
- 淺談動態代理
- 淺談AOP編程
- 淺談ThreadLocal
- 淺談Volatile
- 淺談內存模型
- 淺談類加載
- 專案_數據結構
- 淺談SpareArray
- Android相關
- Android面試題
- 專案
- 推送原理解析
- Lint
- 自定義Lint
- Lint使用
- 優化案
- Apk體積優化
- Kotlin相關
- 簡答1
- 簡答2
- 三方框架相
- Okhttp3源碼分析
- ButterKnife源碼分析
- Glide4源碼分析
- Retrofit源碼分析
- RxJava源碼分析
- ARouter源碼分析
- LeakCanary源碼分析
- WMRouter源碼分析
- 跨平臺相關
- ReactNative
- Flutter
- Hybrid
- 優質源
- 資訊源
- 組件源
- 推薦