Android 编译插桩深入了解

一、前言


JavaPoet 是 square 推出的开源 java 代码生成框架,提供 Java Api 生成 .java 源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

二、使用解析


2.1、示例

为了展示 JavaPoet 的能力,这里以自动生成一个全新的 MainActivity 为例。

public class MainActivity extends Activity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

我在使用 JavaPoet 的时候,习惯从外向内逐一生成,但是这不是标准,这里可以按照自己的方式来理解和生成。示例如下:

    public static void main(String[] args) {
        ClassName activity = ClassName.get("android.app", "Activity");
        TypeSpec.Builder mainActivityBuilder = TypeSpec.classBuilder("MainActivity")
                .addModifiers(Modifier.PUBLIC)
                .superclass(activity);
        ClassName override = ClassName.get("java.lang", "Override");
        ClassName bundle = ClassName.get("android.os", "Bundle");
        ClassName nullable = ClassName.get("android.support.annotation", "Nullable");
        ParameterSpec savedInstanceState = ParameterSpec.builder(bundle, "savedInstanceState")
                .addAnnotation(nullable)
                .build();
        MethodSpec onCreate = MethodSpec.methodBuilder("onCreate")
                .addAnnotation(override)
                .addModifiers(Modifier.PROTECTED)
                .addParameter(savedInstanceState)
                .addStatement("super.onCreate(savedInstanceState)")
                .addStatement("setContentView(R.layout.activity_main)")
                .build();
        TypeSpec mainActivity = mainActivityBuilder.addMethod(onCreate)
                .build();
        JavaFile file = JavaFile.builder("com.test", mainActivity).build();
        try {
            file.writeTo(System.out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

通过在 main() 方法中运行以上的代码就可以直接生成出 MainActivity 对象,自上而下的观察上述代码你会发现 JavaPoet 让 java 文件变得有逻辑性。

2.2、JavaPoet 的常用类

类名作用
TypeSpec用于生成类、接口、枚举对象的类
MethodSpec用于生成方法对象的类
ParameterSpec用于生成参数对象的类
AnnotationSpec用于生成注解对象的类
FieldSpec用于配置生成成员变量的类
ClassName通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
ParameterizedTypeName通过MainClass和IncludeClass生成包含泛型的Class
JavaFile控制生成的Java文件的输出的类

2.3、JavaPoet 的常用方法

设置修饰关键字

addModifiers(Modifier... modifiers)

addModifiers() 方法用来设置修饰关键字,可以传入多个 Modifier 值,Modifier 是一个枚举对象,枚举值为修饰关键字。 所有在 JavaPoet 创建的对象都必须设置修饰符(包括方法、类、接口、枚举、参数、变量)。下面我们看下 Modifier 的源码:

设置注解对象

addAnnotation(AnnotationSpec annotationSpec)
addAnnotation(ClassName annotation)
addAnnotation(Class<?> annotation)

该方法即为类或方法或参数设置注解,参数即可以是 AnnotationSpec,也可以是 ClassName,还可以直接传递 Class 对象。 一般情况下,包含复杂属性的注解一般用 AnnotationSpec,如果单纯添加基本注解无其他附加属性可以直接使用 ClassName 或者 Class 即可。

设置注释

addJavadoc(CodeBlock block)
addJavadoc(String format, Object... args)

在编写类、方法、成员变量时可以通过 addJavadoc() 来设置注释,可以直接传入String 对象或者传入 CodeBlock(代码块)。

2.4、JavaPoet 生成类、接口、枚举对象

在 JavaPoet 中生成类、接口、枚举,必须得通过 TypeSpec 生成,而 classBuilder、interfaceBuilder、enumBuilder 便是创建其关键的方法:

//创建类
TypeSpec.classBuilder("类名“) 
TypeSpec.classBuilder(ClassName className)
//创建接口
TypeSpec.interfaceBuilder("接口名称")
TypeSpec.interfaceBuilder(ClassName className)
//创建枚举
TypeSpec.enumBuilder("枚举名称")
TypeSpec.enumBuilder(ClassName className)

继承、实现接口

//继承类:
.superclass(ClassName className)
//实现接口
.addSuperinterface(ClassName className)

继承存在泛型的父类

当继承父类存在泛型时,需要使用 ParameterizedTypeName

ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments)

返回的 ParameterizedTypeName 对象,已经被添加泛型信息。

方法

addMethod(MethodSpec methodSpec)

通过配置 MethodSpec 对象,使用 addMethod 方法将其添加进 TypeSpec 中。

枚举

addEnumConstan(String enumValue)

通过 addEnumConstan 方法添加枚举值,参数为枚举值名称。

2.5、JavaPoet 生成成员变量

JavaPoet 生成成员变量是通过 FieldSpec 的 build 方法生成:

builder(TypeName type, String name, Modifier... modifiers)

只要传入 TypeName(Class)、name(名称)、Modifier(修饰符),就可以生成一个基本的成员变量。成员变量一般来说由注解(Annotation)、修饰符(Modifier)、注释(Javadoc)、实例化(initializer)。

注解addAnnotation(TypeName name) 
修饰符addModifiers(Modifier ...modifier)
注释addJavadoc(String format, Object... args)
实例化initializer(String format, Object... args)

由于上述前三个方法,都在通用方法介绍过这里就不再重复介绍。下面看看实例化,即成员变量的实例化,例:

public Activity mActivity = new Activity;

而 initializer 方法中的内容就是 “=” 后面的内容,下面看下具体的代码实现,上面的成员变量:

  ClassName activity = ClassName.get("android.app", "Activity");
  FieldSpec spec = FieldSpec.builder(activity, "mActivity")
                .addModifiers(Modifier.PUBLIC)
                .initializer("new $T", activity)
                .build();

2.6、JavaPoet 生成方法

JavaPoet 生成方法分为两种:

构造方法MethodSpec.constructorBuilder()

常规方法

MethodSpec.methodBuilder(String name)

方法的主要构成有方法参数、注解、返回值、方法体、抛出异常五种,注解可以参考通用方法 addAnnotation,其他方法我们将会一一介绍:

方法参数

addParameter(ParameterSpec parameterSpec)

设置方法参数的方法通过 addParameterSpec 来实现,ParameterSpec 的具体使用参考下一小节。

返回值

returns(TypeName returnType)

设置方法的返回值,只需传入一个 TypeName 对象,而 TypeName 是 ClassName,ParameterizedTypeName 的基类。

方法体

在JavaPoet中,设置方法体内容有两个方法,分别是 addCode() 和 addStatement(),这两个本质上都是设置方法体内容,但是不同的是使用 addStatement() 方法时,你只需要专注于该段代码的内容,至于结尾的分号和换行它都会帮你做好。 而 addCode() 添加的方法体内容就是一段无格式的代码片,需要开发者自己添加其格式。

方法体模板

在 JavaPoet 中,设置方法体使用模板是比较常见的,因为 addCode() 和 addStatement() 方法都存在这样的一个重载:

addCode(String format, Object... args)
addStatement(String format, Object... args)

在 JavaPoet 中 format 中存在三种特定的占位符:

$T

$T 在 JavaPoet 代指的是 TypeName,该模板主要将 Class 抽象出来,用传入的 TypeName 指向的 Class 来代替。

ClassName bundle = ClassName.get("android.os", "Bundle");
addStatement("$T bundle = new $T()",bundle)

上述添加的代码内容为:

Bundle bundle = new Bundle();

$N

$N 在 JavaPoet 中代指的是一个名称,例如调用的方法名称、变量名称,这一类存在意思的名称

addStatement("data.$N()",toString)

上述代码添加的内容:

data.toString();

$S

$S 在 JavaPoet 中就和 String.format 中 %s 一样,字符串的模板,将指定的字符串替换到 $S 的地方

.addStatement("super.$S(savedInstanceState)","onCreate")

即将"onCreate"字符串代替到$S的位置上.

抛出异常

.addException(TypeName name)

设置方法抛出异常,可以使用 addException() 方法,传入指定的异常的 ClassName,即可为该方法设置其抛出该异常。

2.7、JavaPoet 生成方法参数

JavaPoet 生成有参方法时,需要填充参数,而生成参数则需要通过 ParameterSpec 这个类。

addParameter(ParameterSpec parameterSpec)

初始化 ParameterSpec

ParameterSpec.builder(TypeName type, String name, Modifier... modifiers)

给参数设置其 Class,以及参数名称和修饰符。

通常来说参数的构成包括:参数的类型(Class)、参数的名称(name)、修饰符(modifiers)、注解(Annotation)。

除了 builder() 方法初始化类型、以及名称、修饰符之外,其余可以通过如下方法进行设置:

添加修饰符

.addModifiers(Modifier modifier)

添加注解

addAnnotation(TypeName name) 

添加修饰符、注解具体使用可参考通用方法。

2.8、JavaPoet 生成注解

在 JavaPoet 创建类、成员变量、方法参数、方法,都会用到注解。如果使用不包含属性的注解可以直接通过:

   .addAnnotation(TypeName name)

直接传入 TypeName 或者 Class 进行设置。

如果使用的注解包含属性并且不止一个时,这时候就需要生成 AnnotationSpec 来解决,下面简单了解下 AnnotationSpec。

初始化AnnotationSpec

AnnotationSpec.builder(ClassName type)

可以发现初始化,只需传入 ClassName 或者 Class 即可。

设置属性

addMember(String name, String format, Object... args)

使用 addMember 可以设置注解的属性值,name 对应的就是属性名称,format 的内容即属性体,同样方法体的格式化在这里也是适用的。

2.9、JavaPoet 如何生成代码

如果上述内容你已经看完,那么恭喜你已经明白 JavaPoet 的意图,但是现在的你还差最后一步,即如何生成代码。JavaPoet 中负责生成的类是 JavaFile:

JavaFile.builder(String packageName, TypeSpec typeSpec)

JavaFile 通过向 build 方法传入 PackageName(Java文件的包名)、TypeSpec(生成的内容)生成。

打印结果

javaFile.writeTo(System.out)

生成的内容会输出到控制台中

生成文件

javaFile.writeTo(File file)

生成的内容会以 java 文件的方式存放到你传入 File 文件的位置。

三、总结


当你读完了本文,如果你产生下面的想法:

  1. JavaPoet 原来还可以这样
  2. JavaPoet 编写过程为什么那么流畅,原来 Java 文件也可以用编程的方式生成
  3. JavaPoet 可不可以改进我的编码流程,提升效率

那么说明你已经对 JavaPoet 感兴趣了,可以自己动手尝试一下感受下 JavaPoet 的魅力。项目主页及源码:https://github.com/square/javapoet

最后贴一张 JavaPoet 辅助图,相信有了它会很好的帮助你使用 JavaPoet。

 


APT 之 Butterknife 源码解析

一、前言


相信大家都用过 ButterKnife ,它是基于编译时注解的框架,能够帮助我们减去每次写 FindViewById 的麻烦。下面我们来分析一下 ButterKnife 的源码

二、使用


关于 ButterKnife 的使用相信不用多说了,大家可以去看一下官网 https://github.com/JakeWharton/butterknife,或者看一下这篇《Android常用之Butterknife使用详解》。下面我们来写个示例:

public class MainActivity extends AppCompatActivity {
 
    @BindView(R.id.tv_content)
    TextView mContentTv;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }
 
    @OnClick(R.id.tv_content)
    void click(View v){
        Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
    }
 
}

build 一下后,生成代码:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;
 
  private View view2131165356;
 
  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }
 
  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;
 
    View view;
    view = Utils.findRequiredView(source, R.id.tv_content, "field 'mContentTv' and method 'click'");
    target.mContentTv = Utils.castView(view, R.id.tv_content, "field 'mContentTv'", TextView.class);
    view2131165356 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.click(p0);
      }
    });
  }
 
  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;
 
    target.mContentTv = null;
 
    view2131165356.setOnClickListener(null);
    view2131165356 = null;
  }
}

下一节我们来分析一下它的原理。

三、源码解析


总的来说,大概可以分为以下两步:

  1. 在编译的时候扫描注解并做相应的处理,调用 javapoet 库生成 java 代码。
  2. 当我们调用 ButterKnife.bind(this) 方法的时候,他会根据类的全限定类型找到相应的代码,并执行完成 findViewById()、setOnClick() 、setOnLongClick() 等操作。

3.1、扫描注解、生成代码

我们知道 ButterKnife 自定义很多的注解,有 BindArray,BindBitmap,BindColor,BindView 等,这里我们以 BindView 为例子讲解就可以了,其他的也是基本类似的,这里就不再讲解了。

首先来看看 BindView 注解的代码:

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

接下来我们看 ButterKnife 注解处理器的代码:

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
  
  private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;
  private Trees trees;
  private int sdk = 1;
 
  @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) {
    }
  }
 
  @Override public Set<String> getSupportedOptions() {
    return Collections.singleton(OPTION_SDK_INT);
  }
 
  @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
  }
 
  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;
  }
 
  @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //1、获取、解析注解
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    //2、生成代码
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
 
      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;
  }
 
...
}

关于注解处理器之前我们已经详细介绍过了,这里就不再赘述,大家可以去看我的《Android 注解处理器解析》,我们直接看一下最关键的 process() 方法代码。process() 方法中分了两步,第一步是获取、解析注解,第二步是生成代码。我们来分别看一下。

3.1.1、获取、解析注解

这里我们进入 findAndParseTargets() 方法,看里面到底是怎样将注解信息存进 map 集合的。这个方法里面针对每一个自定义注解(BindArray,BindBitmap,BindColor,BindView 等) 都做了处理,这里我们重点关注 @BindView 的处理即可。

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
 
    scanForRClasses(env);
 
    // 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);
      }
    }
 
    // 后半部分,待会再讲
    return bindingMap;
  }

我们先来看一下 findAndParseTargets() 方法的前半部分,遍历 env.getElementsAnnotatedWith(BindView.class) 集合,并调用 parseBindView 方法去转化。我们跟进去看看:

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
                           Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
 
    // 判断是否被注解在属性上,如果该属性是被 private 或者 static 修饰的,则出错
    // 判断是否被注解在错误的包中,若包名以“android”或者“java”开头,则出错
    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及其子类或者Interface
    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;
    }
 
    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();
    // 根据所在的类元素去查找 builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    // 如果相应的 builder 已经存在
    if (builder != null) {
        // 验证 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,就需要重新生成,并别存放到  builderMap 中
        builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
 
    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
 
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
 
    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
}

可以看到牵绊部分的主要逻辑在 parseBindView() 方法里面,主要判断被注解 @BindView 修饰的成员变量是不是合法的,private 或者 static 修饰的则出错。parseBindView() 方法分析完毕之后,我们在回过头来看一下 findAndParseTargets() 方法的后半部分,主要做的工作是对 bindingMap 进行重排序。

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    // 省略前半部分
    // 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();
        //获取 type 的父类的 TypeElement
        TypeElement parentType = findParentType(type, erasedTargetNames);
        // 为空,存进 map
        if (parentType == null) {
            bindingMap.put(type, builder.build());
        } else {
             // 获取 parentType 的 BindingSet
            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;
}

到这里为止,我们已经分析完 ButterKnifeProcessor 是怎样处理注解的相关知识,并存进 map 集合中的。

3.1.2、生成代码

下面我们回到 process() 方法,看一下是怎样生成 java 模板代码的:

public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    // 拿到所有的注解信息,TypeElement 作为 key,BindingSet 作为 value
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    // 遍历 map 里面的所有信息,并生成 java 代码
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();
         // 生成 javaFile 对象
        JavaFile javaFile = binding.brewJava(sdk);
        try {// 生成 java 模板代码              
            javaFile.writeTo(filer);
        } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
        }
    }
 
    return false;
}

可以发现这里是调用 JavaPoet 库来生成代码的,关于 javaPoet 的使用可以看我的《Android JavaPoet 使用解析》。我们继续跟进去看看:

JavaFile brewJava(int sdk) {
  return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
      .addFileComment("Generated code from Butter Knife. Do not modify!")
      .build();
}
 
private TypeSpec createType(int sdk) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
            .addModifiers(PUBLIC);
    if (isFinal) {
        result.addModifiers(FINAL);
    }
 
    if (parentBinding != null) {
        result.superclass(parentBinding.bindingClassName);
    } else {
        result.addSuperinterface(UNBINDER);
    }
 
    if (hasTargetField()) {
        result.addField(targetTypeName, "target", PRIVATE);
    }
    // 如果是 View 或者是 View 的子类的话,添加构造方法
    if (isView) {
        result.addMethod(createBindingConstructorForView());
    } else if (isActivity) { // 如果是 Activity 或者是 Activity 的子类的话,添加构造方法
        result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {  // 如果是 Dialog 或者是 Dialog 的子类的话,添加构造方法
        result.addMethod(createBindingConstructorForDialog());
    }
    //  如果构造方法不需要 View 参数,添加 需要 View 参数的构造方法
    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) {
        //生成unBind方法
        result.addMethod(createBindingUnbindMethod(result));
    }
 
    return result.build();
}

接着我们一起来看一下 createBindingConstructor(sdk) 方法:

private MethodSpec createBindingConstructor(int sdk) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
            .addAnnotation(UI_THREAD)
            .addModifiers(PUBLIC);
    // 如果有方法绑定,比如 @onClick,那么增加一个 targetTypeName 类型  的方法参数 target,并且是 final 类型的
    if (hasMethodBindings()) {
        constructor.addParameter(targetTypeName, "target", FINAL);
    } else { // 如果没有 ,不是 final 类型的
        constructor.addParameter(targetTypeName, "target");
    }
    //如果有注解的 View,那么添加 VIEW 类型 source 参数
    if (constructorNeedsView()) {
        constructor.addParameter(VIEW, "source");
    } else {
        //  添加 Context 类型的 context 参数
        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());
    }
    // 如果 @OnTouch 绑定 View,添加 @SuppressLint("ClickableViewAccessibility")
    if (hasOnTouchMethodBindings()) {
        constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
                .addMember("value", "$S", "ClickableViewAccessibility")
                .build());
    }
    // 如果 parentBinding 不为空,调用父类 的构造方法
    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);
        }
        //   遍历  viewBindings,生成  source.findViewById($L) 代码
        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();
}

大概做的事情就是

  • 判断是否有设置监听,如果有监听,将 View 设置为 final。
  • 遍历 viewBindings ,调用 addViewBinding() 生成 findViewById 形式的代码。

下面我们一起来看一下 addViewBinding() 方法是怎样生成代码的。

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();
        // 注意这里直接使用了 target. 的形式,所以属性肯定是不能 private 的
        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;
    }

3.2 ButterKnife 代码注入

ButterKnife 是通过 bind() 方法来实现注入的,即自动帮我们 findViewById ,解放我们的双手提高工作效率。下面我们一起来看一下 bind() 方法是怎样实现注入的:

@NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

可以看到 bind() 方法很简单,逻辑基本都交给 createBinding() 方法去完成。我们一起进入 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());
    // 从 Class 中查找 constructor
    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);
    }
}

其实 createBinding() 主要做了这几件事情:

  • 传入 class ,通过 findBindingConstructorForClass 方法来实例化 constructor
  • 利用反射来初始化 constructor 对象
  • 初始化 constructor 失败会抛出异常

下面我们一起来看一下 findBindingConstructorForClass() 方法是怎样实现的。

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;
    }
    // 如果是 android ,java 原生的文件,不处理
    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);
    }
    // 存进 LinkedHashMap 缓存
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

它的实现思想是这样的:

  • 读取缓存,若缓存命中则直接返回,这样有利于提高效率。从代码中可以看到缓存是通过存进 map 集合实现的。
  • 是否是我们目标文件,是的话进行处理,不是的话直接返回,并打印相应的日志。
  • 利用类加载器加载我们自己生成的 class 文件,并获取其构造方法,获取到则直接返回。获取不到则会抛出异常,在异常的处理中,我们再从当前 class 文件的父类去查找。并把结果存进 map 集合中做缓存处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值