項目結構html
butterknife-runtime butterknife butterknife-annotations butterknife-compiler butterknife-gradle-plugin //如下是輔助 butterknife-integration-test butterknife-lint butterknife-reflect
項目依賴圖:java
如何使用:android
1.先在項目根路徑 build.gradle 裏添加git
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
2.在app module build.gradle 裏添加github
dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.jakewharton:butterknife:10.1.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' implementation 'com.google.android:support-v4:r7' }
butterknife-gradle-plugin 這裏是butterknife插件 對應項目=>butterknife-gradle-pluginapi
com.jakewharton:butterknife:10.1.0 這裏是使用的人調用的butterknife包 =>butterknife & butterknife-annotations & butterknife-runtime緩存
com.jakewharton:butterknife-compiler 這個是註解解析器 => butterknife-compileroracle
總體流程:app
1.先是從使用者經過註釋添加程序進行使用,也就是使用註解。框架
2. 項目在進行構建的時候 經過 annotationProcessor 將對應的註解進行轉換(JavaPoet) 成對應的java ->最終轉成.class
3.程序在執行的時候經過調用 bk的相關函數,完成任務
source code ->.java ->.class -> .dex
(source code ->.java 代碼編譯期解析註解,而且生成新的 Java 文件,減小手動的代碼輸入)
核心用到的庫:
javapoet:是用來生成.java文件 https://github.com/square/javapoet
auto-service:是google提供的,註解 Processor,對其生成配置信息
APT:Annotation Processing Tool 用於編譯期處理註解的api組件,提供2部分:
一、用於模型化Java 程序語言結構的模型化api,包括com.sun.mirror包下的mirror api,javax.lang.model包下的element api 及其餘輔助工具類。
二、javax.annotation.processing包下用於編寫註解處理器的註解處理api。
https://docs.oracle.com/javase/7/docs/technotes/guides/apt/index.html
[java8 使用Pluggable Annotation Processing API 移除了apt]
按項目 進行源碼拆解:
butterknife 這庫是給應用引用的。
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); }
ButterKnife.bind(this) 通過一系列的調用,最終調到如下函數
/** **bindView使用@code source在指定的@code target中註釋的字段和方法 * * @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 { //實例化:執行它的構造函數 也就是 下文的MainActivity_ViewBinding() 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); } }
@Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { //BINDINGS 是綁定的緩存,由於下面.getClassLoader().loadClass 類的反射,創建緩存,會盡可能減小性能損失 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 { //加載clsName "_ViewBinding".java 這個是 【butterknife-compiler】註釋編譯器 在項目編譯時生成的。 //在後面的會說明 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; }
生成如下 ***_ViewBinding
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view7f05001c; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(final MainActivity target, View source) { …… } @Override @CallSuper public void unbind() { …… } }
butterknife-compiler 這庫在項目構建的時候調用的。主要就是將 註解 解析=>生成.java文件。
說白了主要是經過調 ButterKnifeProcessor 裏的process 進行回調完成對註解的解析。butterknife主要是重寫如下方法
init(ProcessingEnvironment processingEnvironment)
裏面提供了Filer等工具類。註解處理器能夠用Filer類建立新文件(源文件、類文件、輔助資源文件)。由此方法建立的源文件和類文件將由管理它們的工具(javac)處理。
getSupportedSourceVersion()
支持JDK的版本
public Set getSupportedAnnotationTypes()
獲取須要處理的註解
public boolean process(annotationsannotations, RoundEnvironment roundEnv)
核心方法,在這裏你能夠掃描和處理註解,並生成java文件。
下面主要分析process處理機制
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //1.查找解析綁定元素 這個是以TypeElement (類,接口,Fragment爲key),BindingSet是這個類上綁定集合 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); //2.遍歷解析生成java 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; }
step1:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); // Process each @BindAnim element. 解析bindAim元素、 for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { //校驗有效性 if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceAnimation(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindAnim.class, e); } } ....省略些吧 基本一個套路 // Process each @BindView element. // env.getElementsAnnotatedWith(BindView.class)獲取全部使用BindView註解的元素 for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } // Process each @BindViews element. for (Element element : env.getElementsAnnotatedWith(BindViews.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindViews(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindViews.class, e); } } // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } // 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). // 將builderMap中的數據添加到隊列中 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) { // 若是找到父類元素,則給當前類元素對應的BindingSet.Builder設置父BindingSet 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; }
/** * 解析bindview . * @param element * @param builderMap * @param erasedTargetNames */ private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { // 首先要注意,此時element是VariableElement類型的,即成員變量 // enclosingElement是當前元素的父類元素,通常就是咱們使用ButteKnife時定義的View類型成員變量所在的類,能夠理解爲以前例子中的MainActivity TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // isInaccessibleViaGeneratedCode必須是類、非靜態、非私有 // 是否在系統相關的資源中使用了ButteKnife註解 boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // Verify that the target type extends from View. TypeMirror elementType = element.asType(); // 若是當前元素是類的成員變量 if (elementType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } Name qualifiedName = enclosingElement.getQualifiedName(); Name simpleName = element.getSimpleName(); // 判斷當前元素是不是 View 的子類,或者是接口,不是的話拋出異常 if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { if (elementType.getKind() == TypeKind.ERROR) { 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 { error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; } } if (hasError) { return; } // 這裏基本就是構建一個資源。 //@BindView(R.id.test) 也就是test在R.java裏的資源值 int id = element.getAnnotation(BindView.class).value(); BindingSet.Builder builder = builderMap.get(enclosingElement); Id resourceId = elementToId(element, BindView.class, id); if (builder != null) { // 若是當前id已經被綁定,則拋出異常 String existingBindingName = builder.findExistingBindingName(resourceId); 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 { // 建立一個新的BindingSet.Builder並返回,而且以enclosingElement 爲key添加到builderMap中 builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); builder.addField(resourceId, new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); }
private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) { BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { builder = BindingSet.newBuilder(enclosingElement); builderMap.put(enclosingElement, builder); } return builder; }
static Builder newBuilder(TypeElement enclosingElement) { 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('.', '$'); // 生成 java類的名稱 ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding"); boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog); }
開始生成.java ,經過JavaPoet
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //查找解析綁定元素 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); //遍歷解析生成java step2 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; }
step2: 根據對應依賴邏輯生成.java
JavaFile brewJava(int sdk, boolean debuggable) { //TypeSpec 表示類、接口、或者枚舉聲明 TypeSpec bindingConfiguration = createType(sdk, debuggable); return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
//這裏就是生成 類的一些方法,變量,函數 。 private TypeSpec createType(int sdk, boolean debuggable) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { //實現unbind result.addSuperinterface(UNBINDER); } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } if (isView) { //若是是ivew result.addMethod(createBindingConstructorForView()); } else if (isActivity) {//若是是activity result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) {//若是是dialog 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, debuggable)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); }
小結:
ButterKnife 整個過程是在項目編譯階段完成的,主要用到了 annotationProcessor 和 JavaPoet 技術,使用時經過生成的輔助類完成操做,調用bind(this)裏面 調用了反射,但經過cache緩存,減小性能損耗,不少人都說運行時沒有用到反射、實際上是不對的。
以上分析是主要核心代碼流程,關於Butterknife裏面的全部的類,不做過多的討論,都是圍繞一個目標進行的,若是還想再細點研究,能夠把源代碼帶入項目進行調試。