在上一篇文章ButterKnife使用篇里面已經詳細介紹過如何使用ButterKnife了,本篇文章將分析它的原理,了解該工具是如何實現綁定控件的. 本文分析基于ButterKnife版本
'com.jakewharton:butterknife:10.2.1'
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(bindLayoutID());
mBind = ButterKnife.bind(this);
initView(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBind != null && mBind != Unbinder.EMPTY) {
mBind.unbind();
mBind = null;
}
}
@Override
protected int bindLayoutID() {
return R.layout.activity_main;
}
這是我們使用ButterKnife時所寫的代碼,那么我們就從 ButterKnife.bind(this)入手,去一步步查看源碼
/**
* BindView annotated fields and methods in the specified {@link Activity}. The current content
* view is used as the view root.
*
* @param target Target activity for view binding.
*/
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
/**
* 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 (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);
}
}
第一個單參數bind(@NonNull Activity target)方法中,獲取了當前window的頂級父View,它是一個FrameLayout,然后調用Unbinder bind(@NonNull Object target, @NonNull View source)方法,這個方法中主要是通過Class類獲取構造器,并獲取一個新的實例.下面我們繼續(xù)查看findBindingConstructorForClass方法和constructor.newInstance(target, source)方法
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@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;
}
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;
}
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 (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;
}
首先從Map中獲取該構造對象,如果存在則直接返回對象,如果不存在,則去構造對應的Constructor.使用Map緩存是為了提高重復使用的效率.
如果類名以"android.","java.","androidx."開頭,則直接返回null,否則創(chuàng)建一個新的Class,名字由傳入的clsName+"_ViewBinding",如果我傳入的類名是ButterKnifeFragment,則返回的實例是ButterKnifeFragment_ViewBinding,查找該文件,可以發(fā)現在build中可以找到,如圖:
文件目錄.png
下面具體看看這個類
public class ButterKnifeFragment_ViewBinding implements Unbinder {
private ButterKnifeFragment target;
private View view7f070046;
@UiThread
public ButterKnifeFragment_ViewBinding(final ButterKnifeFragment target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.button, "field 'mButton' and method 'onClick'");
target.mButton = Utils.castView(view, R.id.button, "field 'mButton'", Button.class);
view7f070046 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick(Utils.castParam(p0, "doClick", 0, "onClick", 0, Button.class));
}
});
target.mEtList = Utils.listFilteringNull(
Utils.findRequiredViewAsType(source, R.id.et_1, "field 'mEtList'", EditText.class),
Utils.findRequiredViewAsType(source, R.id.et_2, "field 'mEtList'", EditText.class),
Utils.findRequiredViewAsType(source, R.id.et_3, "field 'mEtList'", EditText.class));
}
@Override
@CallSuper
public void unbind() {
ButterKnifeFragment target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mButton = null;
target.mEtList = null;
view7f070046.setOnClickListener(null);
view7f070046 = null;
}
}
我在ButterKnifeFragment頁面添加了4個控件(3EditText和一個Button,button綁定了點擊事件,此處只分析button),還記得前面的雙參數bind(@NonNull Object target, @NonNull View source)方法么,就是調用了這個構造方法.它里面有3個函數,一個是findRequiredView,一個是castView,還有一個點擊事件,下面我們一個個查看源碼
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
在findRequiredView里面,我們看到了熟悉的findViewById方法...最終,還是回歸到了系統(tǒng)的API. castView方法也是調用系統(tǒng)的cast方法..
那么,它是如何實現點擊事件防抖動的呢? 繼續(xù)往下看
/**
* A {@linkplain View.OnClickListener click listener} that debounces multiple clicks posted in the
* same frame. A click on one button disables all buttons for that frame.
*/
public abstract class DebouncingOnClickListener implements View.OnClickListener {
static boolean enabled = true;
private static final Runnable ENABLE_AGAIN = () -> enabled = true;
@Override public final void onClick(View v) {
if (enabled) {
enabled = false;
v.post(ENABLE_AGAIN);
doClick(v);
}
}
public abstract void doClick(View v);
}
點擊一次后,讓該控件enabled = false;再調用v.post(ENABLE_AGAIN);恢復控件的狀態(tài).
到此為止,我們了解到ButterKnife底層依舊是使用的findViewById方法,但是我們依舊有個疑問,ButterKnifeFragment_ViewBinding 并非我們自己寫的,它是如何生成的呢?要知道這個就必須了解注解器相關知識了.
回顧我們使用ButterKnife的條件
dependencies {
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
//kapt 'com.jakewharton:butterknife-compiler:10.2.1' //Kotlin
}
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
ButterKnifeProcessor完成了注解處理的核心邏輯,下面我們來看看它是如何實現的.
@AutoService(Processor.class)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
@SuppressWarnings("NullAway") // TODO fix all these...
public final class ButterKnifeProcessor extends AbstractProcessor {
//忽略部分代碼,只查看核心代碼
.......
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
try {
// Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
for (Field field : processingEnv.getClass().getDeclaredFields()) {
if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
field.setAccessible(true);
ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
trees = Trees.instance(javacEnv);
break;
}
}
} catch (Throwable ignored2) {
}
}
}
@Override public Set<String> getSupportedOptions() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
builder.add(OPTION_SDK_INT, OPTION_DEBUGGABLE);
if (trees != null) {
builder.add(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
}
return builder.build();
}
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindAnim.class);
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
@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;
}
....... //忽略部分代碼,只查看核心代碼
}
注意宪睹,ButterKnifeProcessor類上使用了@AutoService(Processor.class)注解并繼承了AbstractProcessor ,來實現注解處理器的注冊樱蛤,注冊到 javac 后账忘,在項目編譯時就能執(zhí)行注解處理器了观谦。
我的ButterKnife相關文章
ButterKnife(一): 使用篇
ButterKnife(二): 原理解析篇
ButterKnife(三): ButterKnifeProcessor解析