Version 10.1.0java
地址 github.com/JakeWharton…android
ButterKnife有兩種實現方式git
源碼下載編譯遇到的一個問題github
直接git clone的Project的名稱是butterknife,和裏面的一個Module重名,致使gradle sync失敗,須要clone時修改下名稱。bash
Application Module和Library Module在使用上有點差異。app
android {
...
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.jakewharton:butterknife:10.1.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
複製代碼
1.2 使用maven
不能給private或static添加,不然報錯ide
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...
}
}
複製代碼
project.gradle函數
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
}
}
複製代碼
build.gradle源碼分析
android {
...
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.jakewharton:butterknife:10.1.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
複製代碼
添加plugin
apply plugin: 'com.jakewharton.butterknife'
複製代碼
2.2 使用 須要用R2替換掉R,由於註解的值只支持常量,而Library Module中的R變量再也不是常量,ButterKnife生成的R2變量都是常量。
class ExampleActivity extends Activity {
@BindView(R2.id.user) EditText username;
@BindView(R2.id.pass) EditText password;
...
}
複製代碼
(1) 從入口ButterKnife.bind()開始
/**
* BindView annotated fields and methods in the specified {@code target} using the {@code source}
* {@link View} as the view root.
*
* @param target Target class for view binding.
* @param source View root on which IDs will be looked up.
*/
@NonNull @UiThread
public static Unbinder bind(@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 {
......
}
}
複製代碼
主要是經過綁定的類,獲取繼承Unbinder的類的構造方法,利用反射建立對象。
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
// 若是已經獲取保存過,則直接返回
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
// 若是是系統類,則返回null
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
// 獲取類名+"_ViewBinding"的類的構造函數
try {
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 () {
......
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
複製代碼
返回類名+"_ViewBinding"的類的構造函數。該類是如何產生的呢?
先別急,這個就是APT在編譯時生成的。咱們先看下這個類的代碼。在Project中build下工程。
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
......
}
複製代碼
可見,View的獲取,仍是經過Android提供的findViewById,和類型轉換。
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.show(p0);
}
});
複製代碼
點擊事件,封裝了一層DebouncingOnClickListener用來防快速點擊。 這種防快速點擊的方式咱們也能夠直接應用到咱們的工程中。
Context context = source.getContext();
Resources res = context.getResources();
target.appname = res.getString(R.string.app_name);
複製代碼
獲取資源的方式,也是經過Resource。
(2) 下面分析下,如何在編譯時生成類
關於APT技術和如何實現就不細說了,網上都有,很簡單。
因此先從ButterKnifeProcessor proces方法開始講起。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); // 第一行
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk, debuggable);//第二行
try {
javaFile.writeTo(filer);//第三行
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
複製代碼
具體生成代碼的邏輯就不細說了,主要就是經過APT技術,遍歷文件,查找自定義註解,封裝成固定格式的javapoet的類,再生成文件。
(3) 說下ButterKnife如何在Module中生成R2文件,將資源定義成常量,編譯在註解中使用,這個咱們開發中也能夠借鑑。 juejin.im/post/5ce3aa…