项目结构
butterknife-runtime
butterknife
butterknife-annotations
butterknife-compiler
butterknife-gradle-plugin
//以下是辅助
butterknife-integration-test
butterknife-lint
butterknife-reflect
项目依赖图:
如何使用:
1.先在项目根路径 build.gradle 里添加
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
2.在app module build.gradle 里添加
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-plugin
com.jakewharton:butterknife:10.1.0 这里是使用的人调用的butterknife包 =>butterknife & butterknife-annotations & butterknife-runtime
com.jakewharton:butterknife-compiler 这个是注解解析器 => butterknife-compiler
整体流程:
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部分:
1、用于模型化Java 程序语言结构的模型化api,包括com.sun.mirror包下的mirror api,javax.lang.model包下的element api 及其他辅助工具类。
2、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里面的所有的类,不作过多的讨论,都是围绕一个目标进行的,如果还想再细点研究,可以把源代码带入项目进行调试。
来源:oschina
链接:https://my.oschina.net/u/3982963/blog/3021144