ButterKnife源码分析(二)
来源:互联网 发布:淘宝查询信誉网站 编辑:程序博客网 时间:2024/04/30 07:17
ButterKnife源码分析(二)
简介
上一篇文章 (ButterKnife源码分析(一))我们分析了ButterKnife是如何将@BindView转换成我们熟悉的findViewById的。
在源码分析的过程中我们提到了一个类名叫MainActivity_ViewBinding,他与MainActivity在同一个包下。是在编译的过程中由ButterKnife自动生成的。今天我们就来看看这个重要的MainActivity_ViewBinding类究竟是如何生成的。
源码网址:https://github.com/JakeWharton/butterknife
最新版本:8.5.1(最新版本的代码与老代码还是有很大变化的,不过好在基本思路没变)
目录
- ButterKnife源码分析二
- 简介
- 目录
- 举个栗子
- 问题一 MainActivity_ViewBinding是如何生成的呢
- findAndParseTargetsenv
- 第一部分
- 第二部分
- bindingbrewJavasdk
- javaFilewriteTofiler
- findAndParseTargetsenv
举个栗子
我们还是举一个简单的例子:有一个MainActivity,他是这样的。
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv_content) TextView tvContent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); }}
编译之后,ButterKnife会生成一个这样的类:
// Generated code from Butter Knife. Do not modify!package com.example.admin.butterknifedemo;import android.support.annotation.CallSuper;import android.support.annotation.UiThread;import android.view.View;import android.widget.TextView;import butterknife.Unbinder;import butterknife.internal.Utils;import java.lang.IllegalStateException;import java.lang.Override;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.tvContent = Utils.findRequiredViewAsType(source, R.id.tv_content, "field 'tvContent'", TextView.class); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.tvContent = null; }}
那么第一个问题来了
问题一: MainActivity_ViewBinding是如何生成的呢
这是一个贯穿整篇文章的问题。首先介绍一下,ButterKnife所使用的注解是编译时注解。(这里不了解注解的,可以先学习下,这里就不再介绍了)顾名思义,编译时注解是在编译时进行解释的注解。想找编译时注解的解释代码很简单,首先找到一个继承AbstractProcessor 的类,然后找到这个类的process方法,入口就在这里了。
这里我们搜索ButterKnife的源码,可以找到一个叫ButterKnifeProcessor的类,这个类继承了AbstractProcessor ,不错这个类中的processor方法,就是我们的着手点。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //对所有的元素进行解析,生成一个bindingMap Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); //遍历整个map for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); //BindingSet 生成一个 javaFile JavaFile javaFile = binding.brewJava(sdk); try { //将javafile转换成真正的java文件 javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }
整个process方法只有三个主要方法 分别是 findAndParseTargets(env)
binding.brewJava(sdk)
javaFile.writeTo(filer)
我们依次看三个方法的作用
findAndParseTargets(env)
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { //创建了两个变量 Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); //第一部分:很多for循环,分别取出ButterKnife对应的注解 // Process each @BindArray element. for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceArray(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } } // Process each @BindBitmap element. for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBitmap(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBitmap.class, e); } } ........ // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds 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); } //第二部分:对builderMap进行处理,生成了最终的 bindingMap // 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; }
这个方法很长(省略号的地方省略了一些)但结构比较清晰,正如我上面的注释所写,整个函数分为两个部分,下面将一一进行讲解,在讲解前我们先看一下process方法的参数RoundEnvironment env,这里我们可以简单的理解为,这个参数记录了整个工程中所有的注解。process方法,在最开始创建了两个变量。
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
这两个变量是干嘛的呢,我们带着这个问题,来看一下第一部分。
第一部分
我们看到第一部分,由十几个for循环构成。这些for循环分别取出了所有的
@BindArray
@BindBitmap
@BindBool
@BindView
@BindViews
………
取出来做了什么,又放到哪里了呢?
// Process each @BindView element.for (Element element : env.getElementsAnnotatedWith(BindView.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); }}
这里我们我们以我们最关心的@BindView为例,看完了这个for循环我们就能推出其他for循环的作用了。
函数首先执行了parseBindView(element, builderMap, erasedTargetNames)方法。注释中我们假设当前解析的元素,就是例子中的
@BindView(R.id.tv_content)TextView tvContent;
下面我们开始分析 parseBindView:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); //这个方法是对当前元素和元素所在的类进行检查,检查什么呢: //1、检查这个元素是不是private或者static的,如果是直接报错!!看看看,如果元素是private的错误就是在这里报出来的 //2、检查private是不是在一个class里(不能在interface、enum等等中),如果不在class中,报错!! //3、检查所在的class是不是private的,如果是private,报错!! boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // 检查@BindView注解修饰的变量,必须是一个view或者interface。如果不是,报错,报错!! 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, 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(id)中的id。 //这里拿到的是R.id.tv_content int id = element.getAnnotation(BindView.class).value(); //从builderMap中取出builder,这里每一个一个builder都对应工程中的一个类。 //也就是说这里拿到的是MainActivity对应的builder。 BindingSet.Builder builder = builderMap.get(enclosingElement); QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) { //如果发现这个builder中已经存在一个变量,id也是R.id.tv_content ,报错!!! //从这里我们可以看出来,一个id只能对应一个变量。 String existingBindingName = builder.findExistingBindingName(getId(qualifiedId)); 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还是空,就创建一个新的。 //这里生成builder后,就直接把builder放到builderMap中了。 builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); //把当前元素tvContent,加入到 MainActivity对应的builder中 builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); }
嚯,这个方法也够长的。不过上面的注释已经非常清楚了。大家可以带着这些注释去看源码。
看到这里是不是有点晕,下面我画一个图,帮大家整理下思路:
首先程序会生成一个名叫builderMap的Map。每一个使用butterKnife的类都会生成一个对应BindingClass。并存放在Map中,bindingClass中用又一个叫viewIdMap的Map,用于存储所有的@bindView。这样我们用于生成MainActivity_ViewBinding类的所有关键信息都被存储到了builderMap中。
这里有个地方要注意,无论是builderMap中的BindingSet.Builder还是viewIdMap中的ViewBinding.builder都是是用来构建BindingSet和ViewBinding的中间产物。
第二部分
第二部分主要的工作就是将builderMap转换成了bindingMap。这两个map名字很像,其实内容也很像。
这里有一点需要注意,无论builderMap还是bindingMap都是有顺序的LinkedHashMap,这两个Map唯一的不同时元素顺序不同,bindingMap会保证父类在子类的前面。以前面的图为例。假设BaseFragment是MainFragment的父类,结果如图:
在遍历之后如果发现MainFragment的父类BaseFragment,且发现BaseFragment还没有添加到buildingMap中,MainFragment就会被放置到队列的尾端。最后添加,以确保,父类一定在子类之前。
其实这里也不用看的这么仔细,只要知道:bindingMap中父类一定在子类之前就可以了。
另外还有一个不同:binderMap中的BindingSet.Builder变成了BindingSet。
binding.brewJava(sdk)
在取得了bindingMap之后,程序会遍历bindingMap中的所用binding,并调用binding.brewJava(sdk),接下来我们就来看一下这个函数做了什么。
JavaFile brewJava(int sdk) { return JavaFile.builder(bindingClassName.packageName(), createType(sdk)) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
这个函数利用binding中的数据生成了一个JavaFile对象,看这一句:
addFileComment("Generated code from Butter Knife. Do not modify!")
是不是很熟悉,对的这就是MainActivity_ViewBinding中的第一句话。接下来我们看一下JavaFile文件:
public final CodeBlock fileComment; public final String packageName; public final TypeSpec typeSpec; public final boolean skipJavaLangImports; private final Set<String> staticImports; private final String indent;
里面存储着:文件注释、包名、变量信息、import等内容。有了这个对象,我们就可以去创造MainActivity_ViewBinding了。
javaFile.writeTo(filer)
接下来是最后一个方法就是真正的写文件,怎么写呢,当然是一行一行的写。哈哈
- 先写注释
- 再写包名
- 再写import
跟我们写代码一样,只不过是用代码写代码…….这里就不再做详细介绍了,有兴趣大家可以看源码。(不影响大局嘛,哈哈)
最后给大家留两个问题。
- MainActivity_ViewBinding文件写完了,但文件的文件名是在哪里确定的呢?
- Set erasedTargetNames这个参数又是做什么用的呢?
butterKnife源码分析,终于写完了。butterKnife是一个简单的工具。源码也并不复杂,还是希望有兴趣的同学可以去看看代码。看源码的效果要比看什么源码解析有用的多。只希望这篇文章,可以给大家一个思路,让大家在源码阅读的路上更加顺畅。
- ButterKnife源码分析(二)
- ButterKnife源码分析二
- ButterKnife源码分析(一)
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- butterknife 源码分析
- butterknife 源码分析
- ButterKnife源码分析
- Butterknife源码分析
- butterknife源码分析:代码分析
- Android Butterknife 框架源码解析(3)——Butterknife 8.7.0源码分析
- 深入理解ButterKnife源码并掌握原理(二)
- 读《STL 源码剖析》及感悟
- 【STL】模拟实现vector
- 运维必修147个命令 只要我写上来的,我都会
- QGraphicsTextItem如何设置文本颜色?
- Git的一点简单命令及文档
- ButterKnife源码分析(二)
- python脚本内运行linux命令的方法
- JavaIO流基础之文件读写
- 神经网络与深度学习(2):梯度下降算法和随机梯度下降算法
- 再论C语言中的指针和数组
- Centos安装Django并创建项目
- 【数学】一个奇技淫巧:如果用一枚硬币生成任意概率——比如1/π?
- # Android文件存储和数据库基本知识
- 博客笔记图片