序言
繼上次介紹了EventBus之后稀并,今天我們來介紹另外一個(gè)好用的三方庫(kù)ButterKnife仅颇,一個(gè)依賴注入框架。該庫(kù)的作者是被稱為Android之神的JakeWarton碘举,我們所熟知的OKHttp忘瓦、Retrofit、ButterKnife殴俱、RxAndroid、RxBinding都是他的杰作枚抵。
在使用ButterKnife之前线欲,我們寫過最多的代碼大概是:
TextView tv = (TextView) findViewById(R.id.tv);
而有了ButterKnife之后,我們就可以告別findViewById()汽摹,ButterKnife會(huì)自動(dòng)幫我們生成這些代碼李丰。接下來,抱著膜拜大神的態(tài)度逼泣,讓我們一起學(xué)習(xí)ButterKnife趴泌。
ButterKnife的使用
ButterKnife的使用流程如下:
- 在工程目錄下的build.gradle文件中添加ButterKnife插件路徑
classpath "com.jakewharton:butterknife-gradle-plugin:8.6.0"
- 在module的build.gradle文件中添加依賴和注解解析器:
api 'com.jakewharton:butterknife:8.8.1'
kapt 'com.jakewharton:butterknife-compiler:8.6.0'
- 接下來在Activity或者Fragment的onCreate/onCreateView方法中綁定當(dāng)前對(duì)象:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Unbinder binder = ButterKnife.bind(this);
}
- 然后在需要注入的View上加注解舟舒,此處以BindView注解為例:
@BindView(R.id.rbStock)
RadioButton rbStock;
//省略其他控件
- 現(xiàn)在,就可以在代碼中使用這些控件嗜憔,而不需要通過findViewById初始化這些控件秃励。
- 最后,在Activity的onDestroy方法中從ButterKnife解綁:
@Override
protected void onDestroy() {
super.onDestroy();
binder.unbind();
}
到這里吉捶,ButterKnife的使用方法介紹完了夺鲜,可以看到使用過程比較簡(jiǎn)單。
源碼分析
1. ButterKnife綁定Activity/Fragment
我們從ButterKnife綁定Activity入手呐舔,來分析原理币励。
這里,我們用ButterKnife綁定項(xiàng)目的MainActivity:
ButterKnife.bind(this);
跟蹤ButterKnife的bind方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
這里會(huì)獲取Activity對(duì)應(yīng)Window的DecorView珊拼,然后調(diào)用createBinding方法:
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);
}
}
著重看一下findBindingConstructorForClass方法:
@Nullable @CheckResult @UiThread
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 {
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;
}
該方法接收一個(gè)Class對(duì)象食呻,即我們的MainActivity對(duì)應(yīng)的Class對(duì)象。首先會(huì)從BINDINGS中尋找是否存在構(gòu)造器澎现,BINDINGS定義如下:
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
它是一個(gè)Map仅胞,鍵是一個(gè)Class對(duì)象,值是一個(gè)構(gòu)造器昔头,至于是什么的構(gòu)造器下面會(huì)揭曉答案饼问。
繼續(xù)上面的分析,如果在BINDINGS中找到構(gòu)造器揭斧,則直接返回構(gòu)造器莱革。如果沒有找到,就校驗(yàn)這個(gè)Class對(duì)象是否合法讹开,如果合法盅视,就執(zhí)行下面一句代碼:
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
加載一個(gè)名為MainActivity_ViewBinding
的類,并且獲取它的構(gòu)造器旦万,并保存在BINDINGS中闹击,下次再bind這個(gè)類的時(shí)候,就直接從BINDINGS中獲取構(gòu)造器成艘,提升性能赏半。
而MainActivity_ViewBinding是通過APT(編譯時(shí)解析技術(shù))生成的類。后面會(huì)介紹具體的生成過程淆两。
再回到createBinding方法断箫,獲取到MainActivity_ViewBinding的構(gòu)造器之后,會(huì)調(diào)用構(gòu)造器的newInstance方法構(gòu)建一個(gè)MainActivity_ViewBinding對(duì)象秋冰。我們看一下MainActivity_ViewBinding的定義:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.rbStock = Utils.findRequiredViewAsType(source, R.id.rbStock, "field 'rbStock'", RadioButton.class);
//省略其他控件初始化代碼
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.rbStock = null;
//省略重復(fù)其他控置空代碼
}
}
這個(gè)類的構(gòu)造方法中初始化了帶@BindView注解的控件仲义,我們看一下具體初始化過程:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
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.");
}
在findRequiredView方法中,我們看見了熟悉的findViewById方法,到這里相信大家都清楚了:ButterKnife的原理是通過APT技術(shù)在編譯過程生成了一個(gè)XX_ViewBinding類埃撵,之后在綁定的時(shí)候赵颅,會(huì)創(chuàng)建XX_ViewBinding對(duì)象,而XX_ViewBinding的構(gòu)造方法中會(huì)幫我們出初始化這些注解修飾的控件暂刘。
MainActivity_ViewBinding實(shí)現(xiàn)了Unbinder接口饺谬,我們看一下接口定義:
public interface Unbinder {
@UiThread void unbind();
Unbinder EMPTY = new Unbinder() {
@Override public void unbind() { }
};
}
很簡(jiǎn)單,只有一個(gè)unbind方法鸳惯,而MainActivity_ViewBinding在unbind方法中把注解對(duì)應(yīng)的控件和target都置為null商蕴,釋放資源。就是使用過程第6步在onDestroy中會(huì)調(diào)用該方法芝发。
2. 編譯時(shí)生成_ViewBinding類
接下來绪商,我們看一下怎樣通過APT技術(shù)生成MainActivity_ViewBinding類。了解APT的同學(xué)會(huì)知道辅鲸,主要是通過AbstractProcessor類在編譯過程中處理注解格郁。
下面,我們就看一下ButterKnife對(duì)應(yīng)注解處理類ButterKnifeProcessor
:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
private static final String OPTION_SDK_INT = "butterknife.minSdk";
static final Id NO_ID = new Id(-1);
static final String VIEW_TYPE = "android.view.View";
static final String ACTIVITY_TYPE = "android.app.Activity";
static final String DIALOG_TYPE = "android.app.Dialog";
private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList";
private static final String BITMAP_TYPE = "android.graphics.Bitmap";
private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable";
private static final String TYPED_ARRAY_TYPE = "android.content.res.TypedArray";
private static final String NULLABLE_ANNOTATION_NAME = "Nullable";
private static final String STRING_TYPE = "java.lang.String";
private static final String LIST_TYPE = List.class.getCanonicalName();
private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(OnCheckedChanged.class, OnClick.class, OnEditorAction.class, OnFocusChange.class, OnItemClick.class, OnItemLongClick.class, OnItemSelected.class, OnLongClick.class, OnPageChange.class, OnTextChanged.class, OnTouch.class);
private static final List<String> SUPPORTED_TYPES = Arrays.asList("array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string");
private Elements elementUtils;
private Types typeUtils;
private Filer filer;
private Trees trees;
private int sdk = 1;
private final Map<QualifiedId, Id> symbols = new LinkedHashMap();
public ButterKnifeProcessor() {
}
//初始化一些參數(shù)和工具類
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = (String)env.getOptions().get("butterknife.minSdk");
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException var5) {
env.getMessager().printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support.");
}
}
this.elementUtils = env.getElementUtils();
this.typeUtils = env.getTypeUtils();
this.filer = env.getFiler();
try {
this.trees = Trees.instance(this.processingEnv);
} catch (IllegalArgumentException var4) {
;
}
}
//獲取該P(yáng)rocessor能處理的注解集合
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet();
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(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
//主要的處理方法独悴,分析將從這里開始
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = this.findAndParseTargets(env);
Iterator var4 = bindingMap.entrySet().iterator();
while(var4.hasNext()) {
Entry<TypeElement, BindingSet> entry = (Entry)var4.next();
TypeElement typeElement = (TypeElement)entry.getKey();
BindingSet binding = (BindingSet)entry.getValue();
JavaFile javaFile = binding.brewJava(this.sdk);
try {
javaFile.writeTo(this.filer);
} catch (IOException var10) {
this.error(typeElement, "Unable to write binding for type %s: %s", typeElement, var10.getMessage());
}
}
return false;
}
//省略很多方法
}
我們就直接從process方法開始分析例书,這個(gè)方法的邏輯比較簡(jiǎn)單,通過findAndParseTargets方法找到所有使用ButterKnife的類刻炒,然后根據(jù)注解信息生成對(duì)應(yīng)的_ViewBinding類决采。
可以把這段代碼分為兩個(gè)過程:處理注解并記錄信息與生成_ViewBinding類。
a. 處理注解并記錄信息
先看一下findAndParseTargets方法:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, Builder> builderMap = new LinkedHashMap();
Set<TypeElement> erasedTargetNames = new LinkedHashSet();
this.scanForRClasses(env);
//省略處理其他注解的代碼
var4 = env.getElementsAnnotatedWith(BindView.class).iterator();
while(var4.hasNext()) {
element = (Element)var4.next();
try {
this.parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception var12) {
this.logParsingError(element, BindView.class, var12);
}
}
//省略處理其他注解的代碼
var4 = LISTENERS.iterator();
while(var4.hasNext()) {
Class<? extends Annotation> listener = (Class)var4.next();
this.findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
Deque<Entry<TypeElement, Builder>> entries = new ArrayDeque(builderMap.entrySet());
LinkedHashMap bindingMap = new LinkedHashMap();
while(!entries.isEmpty()) {
Entry<TypeElement, Builder> entry = (Entry)entries.removeFirst();
TypeElement type = (TypeElement)entry.getKey();
Builder builder = (Builder)entry.getValue();
TypeElement parentType = this.findParentType(type, erasedTargetNames);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = (BindingSet)bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
entries.addLast(entry);
}
}
}
return bindingMap;
}
這個(gè)方法很長(zhǎng)坟奥,主要的作用就是找到項(xiàng)目中所有使用ButterKnife注解的類树瞭,分別處理他們,最后把他們的信息保存在一個(gè)Map中:
LinkedHashMap<ElementType, BindingSet> bindingMap = new LinkedHashMap();
這個(gè)Map以ElementType(使用注解的類)為key爱谁,值則是一個(gè)BindingSet對(duì)象(記錄了類中用到的注解及作用對(duì)象等信息)晒喷。后面就是通過這個(gè)Map生成對(duì)應(yīng)的_ViewBinding類。
接下來访敌,我們分析一下注解BindView的處理過程凉敲,主要是通過parseBindView方法處理:
private void parseBindView(Element element, Map<TypeElement, Builder> builderMap, Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
//檢查是否滿足使用注解的條件
boolean hasError = this.isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || this.isBindingInWrongPackage(BindView.class, element);
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable)elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
//不滿足條件,拋出異常
if (!isSubtypeOfType(elementType, "android.view.View") && !this.isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
this.note(element, "@%s field with unresolved type (%s) must elsewhere be generated as a View or interface. (%s.%s)", BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
} else {
this.error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
//滿足條件寺旺,開始解析注解
if (!hasError) {
int id = ((BindView)element.getAnnotation(BindView.class)).value();
//從緩存builderMap中查找注解使用類對(duì)應(yīng)的builder對(duì)象
Builder builder = (Builder)builderMap.get(enclosingElement);
QualifiedId qualifiedId = this.elementToQualifiedId(element, id);
String existingBindingName;
if (builder != null) {
existingBindingName = builder.findExistingBindingName(this.getId(qualifiedId));
if (existingBindingName != null) {
this.error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBindingName, enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
//如果緩存中沒有找到注解使用類對(duì)應(yīng)的builder對(duì)象爷抓,則創(chuàng)建這個(gè)對(duì)象,并且保存在builderMap中
builder = this.getOrCreateBindingBuilder(builderMap, enclosingElement);
}
existingBindingName = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//把注解BindView修飾的字段添加到builder中
builder.addField(this.getId(qualifiedId), new FieldViewBinding(existingBindingName, type, required));
erasedTargetNames.add(enclosingElement);
}
}
代碼中關(guān)鍵的地方給出了注釋阻塑,各位同學(xué)可以根據(jù)注釋自行理解代碼蓝撇。其他注解的處理方式跟注解BindView類似,這里不做介紹叮姑。就這樣唉地,把類和它對(duì)應(yīng)的注解信息都保存到Map中。
b. 生成_ViewBinding類
我們?cè)賮砜匆幌聀rocess方法:
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = this.findAndParseTargets(env);
Iterator var4 = bindingMap.entrySet().iterator();
while(var4.hasNext()) {
Entry<TypeElement, BindingSet> entry = (Entry)var4.next();
TypeElement typeElement = (TypeElement)entry.getKey();
BindingSet binding = (BindingSet)entry.getValue();
JavaFile javaFile = binding.brewJava(this.sdk);
try {
javaFile.writeTo(this.filer);
} catch (IOException var10) {
this.error(typeElement, "Unable to write binding for type %s: %s", typeElement, var10.getMessage());
}
}
return false;
}
通過第一步得到了bindingMap對(duì)象传透,接下來遍歷bindingMap耘沼,通過BindingSet的brewJava方法生成對(duì)應(yīng)的ViewBinding類:
JavaFile brewJava(int sdk) {
return JavaFile.builder(this.bindingClassName.packageName(), this.createType(sdk)).addFileComment("Generated code from Butter Knife. Do not modify!", new Object[0]).build();
}
static BindingSet.Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
boolean isView = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.view.View");
boolean isActivity = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.app.Activity");
boolean isDialog = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.app.Dialog");
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName)targetType).rawType;
}
String packageName = MoreElements.getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$');
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding", new String[0]);
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new BindingSet.Builder((TypeName)targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
這里就不深入介紹生成ViewBinding類的過程,它使用到了javapoet技術(shù)朱盐,javapoet技術(shù)是square公司開源的群嗤,專門用來生成java源碼,很多使用APT技術(shù)的庫(kù)都是使用到j(luò)avapoet技術(shù)兵琳。
總結(jié)
到這里狂秘,ButterKnife的原理我們就介紹完了,各位同學(xué)按照本文思路躯肌,對(duì)著ButterKnife源碼進(jìn)行學(xué)習(xí)者春。
如果各位同學(xué)使用過kotlin,就知道kotlin中不需要ButterKnife清女,kotlin提供了插件钱烟,可以直接獲取xml中的控件,還提供了一個(gè)功能強(qiáng)大又易用的庫(kù)anko嫡丙,有興趣的小伙伴可以了解一下拴袭。