Butter Knife 是一個開源的依賴注入框架, 主要起到一個語法糖的效果, 好比 Button button = (Button) findViewById(R.id.button);
, 就能夠簡化成 @BindView(R.id.button) Button button;
. 詳細的能夠看這裏. 顯然能夠看出是使用註解作到的, 至因而編譯時註解仍是運行時註解, 下面會開始分析. 這裏使用的版本是 8.6.0, 簡單起見, 本篇只分析 View 的注入.java
這裏是一個 activityandroid
public class LoginActivity extends Activity { @BindView(R.id.loginButton) Button button; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login_layout); ButterKnife.bind(this); } ...... }
因此是怎麼注入的呢 ? 感受應該和 ButterKnife.bind(this);
有關.
該方法以下git
@NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); }
繼續點開 createBindinggithub
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); } }
簡化一下也就是這樣segmentfault
private static Unbinder createBinding(Object target, View source) { Class<?> targetClass = target.getClass(); Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } return constructor.newInstance(target, source); // 異常處理 ... }
Unbinder
是一個接口, 聲明以下緩存
public interface Unbinder { @UiThread void unbind(); Unbinder EMPTY = new Unbinder() { @Override public void unbind() { } }; }
大概邏輯就是找到要綁定的那個類的構造函數, 而後利用反射創造出實例, 這裏的聲明是 Constructor<? extends Unbinder>
, 因此返回的是一個 Unbinder
實例. 若是沒找到構造函數, 就返回一個 new 出來的 Unbinder
.app
繼續點開 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; }
稍微簡化一下ide
private static Constructor<? extends Unbinder> findxxx(Class<?> cls) { // 首先在緩存中尋找 Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { return bindingCtor; } // 緩存中沒有 String clsName = cls.getName(); // 是不是 android 源碼中的文件 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"); bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { // 繼續在父類中尋找 bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } // 異常處理 // 放入緩存 BINDINGS.put(cls, bindingCtor); return bindingCtor; }
BINDINGS
是一個 map, 聲明以下函數
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
做用是緩存類對應的構造函數
因此 findBindingConstructorForClass
的主要過程是這樣的:
在緩存中尋找某個類的構造函數
有 -> 返回構造函數
沒有 -> 轉 2
是不是 android 源碼中的文件
是 -> 返回 null
沒有 -> 轉 3
取得某個類的構造器, 這個類的名字是 clsName + "_ViewBinding"
出異常則將參數換爲父類, 返回 1
將構造函數放入緩存
返回構造函數
這裏要注意的有 3 個:
這裏用 map 做爲緩存, 是爲了加快速度, 由於反射的效率是很低的, 而移動設備對性能的要求比較高
之因此要添加第 2 步, 是由於要在父類中尋找, 可能找到 android 源碼裏去.
這裏找了好久的構造函數, 並非傳入的類的, (啊這不是廢話嗎), 是 clsName_ViewBinding
這個類的. 在這裏的話應該叫 LoginActivity_ViewBinding.java
終於找到和 View 注入相關的東西了.如今有 1 個問題, 這是啥 ?
在 intellij idea
中, 使用 ctrl + shift + n
組合鍵, 能夠找到這個文件, 路徑以下: app\build\generated\source\apt\debug\...\LoginActivity_ViewBinding.java
public class LoginActivity_ViewBinding implements Unbinder { private LoginActivity target; private View view2131558546; @UiThread public LoginActivity_ViewBinding(LoginActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public LoginActivity_ViewBinding(final LoginActivity target, View source) { this.target = target; View view; // findRequiredView 中利用 findViewById 找到 view view = Utils.findRequiredView(source, R.id.loginButton, "field 'button' and method 'login'"); // castView 中利用 cls.cast(view); 將 view 強轉成 button target.button = Utils.castView(view, R.id.loginButton, "field 'button'", Button.class); view2131558546 = view; // other } @Override @CallSuper public void unbind() { LoginActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.button = null; view2131558546 = null; } }
咱們如今來捋一捋
以前調用的 constructor.newInstance(target, source);
其實調用了 LoginActivity_ViewBinding(final LoginActivity target, View source)
, 返回的 Unbinder
實例也就是 LoginActivity_ViewBinding
實例, 因此一開始的 ButterKnife.bind(this);
方法的返回值就是這個實例, 這個實例只有一個方法 unbind()
, 用於解除當前 activity 的綁定.
在構造方法中, 用 findViewById
找到對應的 view 並強轉成 button, 而後直接賦給 target.button
, 因此用 Butter Knife
時, 用註解標註的內容不能是 private 的, 不然會拿不到這個成員變量.
因此 Butter Knife
實現依賴注入的方法就是額外生成一個類, 在類的構造函數中寫入咱們偷懶沒寫的 findViewById
等方法, 並添加一個解除綁定的方法.
在調用 ButterKnife.bind(xxx);
方法時會利用反射生成額外類的實例, 此時綁定便完成了.
到這裏就解釋完了是怎麼實現依賴注入的, 接下來研究這個類是怎麼生成的.
既然是額外生成輔助類實現的, 那麼能夠確定是利用編譯時註解. 其實直接看 BindView
的定義, 會發現是 CLASS
級別的, 因此固然是用註解處理器處理的. 關於編譯時註解參見上一篇, 其中提到了接下來比較重要的 element
.
在 github
上看 Butter Knife
的源碼, 能夠發現目錄結構以下
butterknife // 上面分析的代碼都在這裏
butterknife-annotations // 自定義註解
butterknife-compiler // 註解處理
butterknife-gradle-plugin // gradle 插件
butterknife-lint // lint 檢查
在 butterknife-compiler
包下有 ButterKnifeProcessor.java
文件, 這就是咱們要找的註解處理器了.
類的聲明以下 public final class ButterKnifeProcessor extends AbstractProcessor
咱們先看 init()
方法
private Elements elementUtils; // element 輔助類 private Types typeUtils; // 操做 TypeMirror 的輔助類 private Filer filer; // 文件操做輔助類 @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."); } } elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { } }
能夠看到主要是對一些輔助類進行了初始化.
接着看最重要的 process
方法
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { // 解析每一個註解 // TypeElement 表示類或接口級別的元素 // BindingSet 是自定義的一個類, 表示全部須要綁定的元素的信息的集合 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); // 利用 javapoet 生成 java 文件, 也就是 xxx_ViewBinding.java JavaFile javaFile = binding.brewJava(sdk); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }
這裏利用 javapoet 進行代碼生成, 這是同做者寫的另外一個開源的代碼生成框架.
這裏先無論這個, 往下看 findAndParseTargets
實現
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { // 這個 map 的 key 是 類或者接口 類型的 element, value 是對應的 builder, 用於存儲要生成的輔助類的一些信息 Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); // 其餘註解的處理 // 處理每一個用 BindView 註解標註的元素, 這裏的 Element 都是 Field 級別的 for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } // Associate superclass binders with their subclass binders. This is a queue-based tree walk // which starts at the roots (superclasses) and walks to the leafs (subclasses). Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast(entry); } } } return bindingMap; }
主要邏輯寫在了 parseBindView
裏.
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { // 獲得父元素, 這裏通常是 activity 類 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // 檢查元素是否可達(private? public? 是否寫在類的成員變量裏?) 是否在錯誤的包裏 (安卓源碼 ?) boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // 類型檢查這裏被我省略了 if (hasError) { return; } // 獲得 view 的 id int id = element.getAnnotation(BindView.class).value(); // 檢查緩存中是否有這個 activity BindingSet.Builder builder = builderMap.get(enclosingElement); // QualifiedId 就是帶上 activity 所在包名的信息的 id QualifiedId qualifiedId = elementToQualifiedId(element, id); // 檢查這個 id 是否已經被綁定過了 if (builder != null) { String existingBindingName = builder.findExistingBindingName(getId(qualifiedId)); // 若是不爲 null 說明已經被綁定過了, 直接返回 if (existingBindingName != null) { 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 { // 若是 builder 不存在, 就建立一個新的, 並放入 map 中 builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); // 在該 builder 中添加新的 field 信息, 即關於綁定的信息 builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); }
這裏比較重要的是 builder 的建立過程, 即 getOrCreateBindingBuilder
private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) { BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { // 傳入父元素, 即類信息建立 builder builder = BindingSet.newBuilder(enclosingElement); builderMap.put(enclosingElement, builder); } return builder; }
其中 BindingSet.newBuilder
以下
static Builder newBuilder(TypeElement enclosingElement) { // 利用 typemirror 能夠得到元素的詳細類型信息 TypeMirror typeMirror = enclosingElement.asType(); boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE); boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE); boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE); TypeName targetType = TypeName.get(typeMirror); if (targetType instanceof ParameterizedTypeName) { targetType = ((ParameterizedTypeName) targetType).rawType; } String packageName = getPackage(enclosingElement).getQualifiedName().toString(); String className = enclosingElement.getQualifiedName().toString().substring( packageName.length() + 1).replace('.', '$'); // 這裏是 javapoet 的語法, 後一個參數是類名, 即咱們要找的 xxx_ViewBinding ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding"); boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog); }
綜上, 每處理一個 @BindView
註解標註的元素, 會經歷以下過程
獲得該元素的父元素, 通常爲 activity
合法性檢查
檢查 builderMap 中是否有該父元素對應的值 (builerMap 用於緩存, key 是類或者接口 類型的元素, value 是對應的 builder, builder 用於存儲要生成的輔助類的類名, 其中須要綁定的元素(如 field), 以及一些其餘信息 )
若是沒有, 新建相應的鍵值對, 這時 builder 包含一些類自己的信息, 如類名, 是不是 final, 是不是 activity 等等
若是有, 獲取 id, 並檢查 id 是否已被綁定, 被綁定則報錯
在得到的 builder 中添加該元素的相關信息, 該信息用於生成在 xxx_ViewBinding
的構造函數中的查找及賦值的相關代碼.
builderMap 會通過一些處理(父類子類關係的調整等), 最後轉變成 BindingSet, 也就是咱們一開始看到的 Map<TypeElement, BindingSet> bindingMap
這裏完成了類信息的構建, 接下來還有最後一步, 代碼生成
代碼生成是這句話: JavaFile javaFile = binding.brewJava(sdk);
, 得到的 javaFile
包含了整個類的信息, 點進 brewJava
看看.
JavaFile brewJava(int sdk) { return JavaFile.builder(bindingClassName.packageName(), createType(sdk)) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
這裏重要的是 createType
// 利用 bindingset 中的信息生成最終的輔助類 private TypeSpec createType(int sdk) { // 添加 public 修飾 // TypeSpec 用於生成類 TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); // 是不是 final if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { // 繼承 Unbinder 的代碼在這 result.addSuperinterface(UNBINDER); } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } // 建立對應的構造函數 if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor()); } result.addMethod(createBindingConstructor(sdk)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); }
這裏是構造函數的生成
private MethodSpec createBindingConstructor(int sdk) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC); if (hasMethodBindings()) { constructor.addParameter(targetTypeName, "target", FINAL); } else { constructor.addParameter(targetTypeName, "target"); } if (constructorNeedsView()) { constructor.addParameter(VIEW, "source"); } else { constructor.addParameter(CONTEXT, "context"); } if (hasUnqualifiedResourceBindings()) { // Aapt can change IDs out from underneath us, just suppress since all will work at runtime. constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType") .build()); } if (hasOnTouchMethodBindings()) { constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT) .addMember("value", "$S", "ClickableViewAccessibility") .build()); } if (parentBinding != null) { if (parentBinding.constructorNeedsView()) { constructor.addStatement("super(target, source)"); } else if (constructorNeedsView()) { constructor.addStatement("super(target, source.getContext())"); } else { constructor.addStatement("super(target, context)"); } constructor.addCode("\n"); } if (hasTargetField()) { constructor.addStatement("this.target = target"); constructor.addCode("\n"); } if (hasViewBindings()) { if (hasViewLocal()) { // Local variable in which all views will be temporarily stored. constructor.addStatement("$T view", VIEW); } for (ViewBinding binding : viewBindings) { addViewBinding(constructor, binding); } for (FieldCollectionViewBinding binding : collectionBindings) { constructor.addStatement("$L", binding.render()); } if (!resourceBindings.isEmpty()) { constructor.addCode("\n"); } } if (!resourceBindings.isEmpty()) { if (constructorNeedsView()) { constructor.addStatement("$T context = source.getContext()", CONTEXT); } if (hasResourceBindingsNeedingResource(sdk)) { constructor.addStatement("$T res = context.getResources()", RESOURCES); } for (ResourceBinding binding : resourceBindings) { constructor.addStatement("$L", binding.render(sdk)); } } return constructor.build(); }
由於生成的代碼都很相似, 這裏挑 addViewBinding(constructor, binding);
看一下
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) { if (binding.isSingleFieldBinding()) { // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = binding.getFieldBinding(); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName()); boolean requiresCast = requiresCast(fieldBinding.getType()); if (!requiresCast && !fieldBinding.isRequired()) { builder.add("source.findViewById($L)", binding.getId().code); } else { builder.add("$T.find", UTILS); builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, $L", binding.getId().code); if (fieldBinding.isRequired() || requiresCast) { builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { builder.add(", $T.class", fieldBinding.getRawType()); } builder.add(")"); } result.addStatement("$L", builder.build()); return; } List<MemberViewBinding> requiredBindings = binding.getRequiredBindings(); if (requiredBindings.isEmpty()) { result.addStatement("view = source.findViewById($L)", binding.getId().code); } else if (!binding.isBoundToRoot()) { result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, binding.getId().code, asHumanDescription(requiredBindings)); } addFieldBinding(result, binding); addMethodBindings(result, binding); }
代碼很簡單, 能夠看到 view = $T.findRequiredView(source, $L, $S)
表明生成的代碼中的 view = Utils.findRequiredView(source, R.id.loginButton, "field 'button' and method 'login'");
代碼生成部分與 javapoet
關係比較強, 因此仍是要先了解 javapoet
才能比較深刻地瞭解, 這裏就不繼續分析了.
總結一下, Butter Knife
主要是利用編譯時註解和 javapoet
在編譯時動態生成輔助類, 在 bind
方法運行時運用反射建立輔助類的實例, 起到了語法糖的效果==.