依赖注入框架ButterKnife的使用与原理解析

1. 注解与依赖注入

1.1 注解

 从JDK 5.0开始,Java增加了对注解(Annotation)的支持,注解是代码里的特殊标记,这些标记可以再编译类加载运行时被读取,并执行相关的处理。通过使用注解,开发人员可以再不改变原有逻辑的情况下,在源文件嵌入一些补充的信息,这些信息被存储在Annotation的元数据中。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。从某些程度来说,注解就像修饰符一样被使用,可以用于修饰包、类、构造器、方法、成员变量、成员方法等。Annotation是一个接口,使用@interface标志,其定义如下:

@元注解1(...)
@元注解2(...)
...
public @interface 注解名称 {
    数据类型 变量名() default 默认值;  // 元数据或称成员变量
    ...
}

1.1.1 注解分类

 注解分为标准注解元注解自定义注解,其中,标准注解元注解由JDK实现,而自定义注解由我们根据项目需求自己定义,需要用到元注解。

(1)标准注解

 JDK提供了4种标准注解:@Override@Deprecated@SuppressWarnings@SafeVarargs,这四个标准注解为类、成员方法等程序元素提供基本的注解修饰功能,它们被定于在java.lang包下。

  • @Override

 @Override用来指定方法的重载的,它可以强制一个子类必须要覆盖父类的方法,如果我们在子类中没有覆写被@Override注解修饰的方法,程序在编译检查时就会报错。@Override的定义如下:

@Target(ElementType.METHOD)  // 元注解,表示当前注解只能修饰方法
@Retention(RetentionPolicy.SOURCE) // 元注解,表示当前注解在源码中有效
public @interface Override {
}
  • @Deprecated

 @Deprecated用于表示某个类、方法、变量、构造方法等已经过时,当其他程序使用已过时的类、方法等程序元素时,编译器将会发出警告,但是并不会报错,该注解的目的是告诉开发者有新的可以用。当我们遇到被@Deprecated注解的程序元素,可以根据它的提示使用推荐的方式。@Deprecated的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME) 
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
  • @SuppressWarnings

  @SuppressWarnings选择性地取消特定代码段中的编译器警告,它的定义如下:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    // 元数据(成员变量)
    // 使用name=value设置,如:@SuppressWarnings(value="unchecked")
    String[] value();
}
  • @SafeVarargs

 @SafeVarargs是JDK7新增的标准注解,用来声明使用了可变长度参数的方法或构造方法,其在与泛型类一起使用时不会出现类型安全问题。它的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}

(2)元注解

 元注解也是一种注解,它的作用为用来注解其他注解,从而创建新的注解。JDK提供了5种元注解:

  • @Targe

 @Targe表示注解所修饰的对象范围。@Targe的注解取值是一个ElementType类型(枚举)的数据,其中有以下几种取值,对应不同的对象范围。ElementType定义如下:

public enum ElementType {
    TYPE,   // 能修饰类、接口或枚举类型
    FIELD,  // 能修饰成员变量
    METHOD, // 修饰成员方法
    PARAMETER, // 修饰参数
    CONSTRUCTOR, // 能修饰构造方法
    LOCAL_VARIABLE,// 能修饰局部变量
    ANNOTATION_TYPE,// 能修饰注释
    PACKAGE,// 能修饰包
    TYPE_PARAMETER,// 能修饰参数声明
    TYPE_USE // 使用类型
}
  • @Retention

 @Retention用来声明注解的保留策略。@Targe的注解取值是一个RetentionPolicy类型(枚举)的数据,其中有以下三种取值,对应不同级别的保留策略,这些策略生命周期长度为:SOURCE<CLASS<RUNTIME。RetentionPolicy定义如下:

public enum RetentionPolicy {
    // 源码级注解。注解信息只会保留在.java源码中,源码在编译后,
    // 注解信息被丢弃,不会保留在.class中。
    SOURCE,
    // 编译时注解。注解信息会保留在.java 源码以及.class 中。
    // 当运行Java程序时,JVM会丢弃该注解信息,不会保留在JVM中。
    CLASS,
    // 运行时注解。当运行Java程序时,除了.java 源码以及.class 中,JVM也会保留该注解信息
    // 可以通过反射获取该注解信息。
    RUNTIME
}
  • @Inherited:表示注解可以被继承。

  • @Documented:表示这个注解应该被JavaDoc工具记录。

  • @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次使用。

(3)自定义注解

 根据第1.1处注解的定义,自定义一个注解非常简单,这里以定义一个Author注解为例,该注解使用范围仅限方法,支持在运行时去动态获取注解信息。Author注解如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
    String name() default "隔壁老王";
    int age() default 18;
    String csdnId;  // 没有默认值,在使用注解是必须传入
}
// 注:假如自定义注解只有一个成员变量,可以直接将变量名称设为"value"
// 在使用注解的时候,就不用了使用"变量名=值",直接传入“值”即可。
// 如@Target的定义:
// @Documented
// @Retention(RetentionPolicy.RUNTIME)
// @Target(ElementType.ANNOTATION_TYPE)
// public @interface Target {
//    ElementType[] value();
// }

 使用Author注解:

public class MyTestClass {
    
    @Author(name="蒋东国", csdnId="AndrExpert")
    public void writeBlog() {
        ...
    }
}

1.1.2 注解处理器

 如果仅仅是定义一个注解,没有处理注解的解析工具,那这个注解所起的作用有限,而这个处理注解的工具就是注解处理器。对于不同的注解有着不同的注解处理器,比如对于运行时注解来说,通常使用反射机制实现注解处理器;对于编译时注解会采用AbstractProcessor实现注解处理器,AbstractProcessor是JDK提供的注解处理器,它又继承于Processor接口,它们均位于javax.annotation.processing包中。下面我们分析下这两种情况:

  • 运行时注解处理器

 运行时注解处理器用于处理运行时注解,它通过反射机制实现,而运行时注解是指保留策略只能为RUNTIME的注解。下面我们定义一个运行时注解:

@Target(ElementType.METHOD)          // 注解所修饰的对象为成员方法
@Retention(RetentionPolicy.RUNTIME)  // 保留策略为Runtime
public @interface Author {
    String name() default "隔壁老王";
    int age() default 18;
    String csdnId;  // 没有默认值,在使用注解是必须传入
}

 使用Author注解:

public class MyTestClass {
    
    @Author(name="蒋东国", scdnId="laojiang")
    public void writeBlog() {
        ...
    }
}

 接下来,我们自定义一个注解处理器,使用反射实现。

public class MyAnnotionProcessor {
    public static void main(String[] args) {
        // 通过反射获取MyTestClass的所有方法
        Method[] methods = MyTestClass.class.getDeclaredMethods();
        // 遍历所有Method
        // MyTestClass可能有多个方法被Author注解修饰
        for(Method m: methods) {
            // 获取Author注解对象
            Author author = m.getAnntation(Author.class);
            // 获取Author注解的成员变量
            String name = author.name();
        }
    }
}
  • 编译时注解处理器

APT,即Annotation Processing Tool,编译时注解处理器,它是javac的一个工具,可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,并根据这些信息自动生成一个代码,我们可以指定将这些代码保存在Java文件中。由于获取注解和生成代码都是在代码编译时完成的,故比反射在运行时处理注解大大提高了程序的性能。APT技术被广泛运行在第三方框架中,它的核心是AbstractProcessor类,如果我们需要实现一个编译时注解处理器,就需要定义一个类继承它。另外,APT项目通常由两个Java Library模块组成:Annotation模块和Compiler模块,其中,Annotation模块用来存放自定义的注解,而Compiler模块用来实现注解处理器,即包含继承AbstractProcessor的类,它依赖Annotation模块。app模块的gradle中依赖如下:

dependencies {
    implementation project(path: ':annotations')
    annotationProcessor project(path: ':compiler')
}

下面我们举个例子:
 (1)首先,我们新建一个Java Library module(注:编译时起作用,不能是Android Library module)来专门存放注解,并将这个Library命名为"annotations",然后创建两个示例注解:BindView、BindString。代码如下:

/** BindView注解
 * author : jiangdg
 * date   : 2019/12/22 22:37
 * desc   : 只能注解成员变量,保留策略为SOURCE(即注解信息只保留到源码层)
 *     当然也可以是 CLASS和 RUNTIME,但是没有必要
 * version: 1.0
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value() default 1;
}

/** BindString注解
 * author : jiangdg
 * date   : 2019/12/22 22:41
 * desc   : 只能注解成员变量,保留策略为SOURCE
 * version: 1.0
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindString {
    String value() default "";
}

 (2)其次,再新建一个Java Library module来实现注解处理器,并将这个Library命名为"compiler",同时创建注解处理器,该类继承于AbstractProcessor,通常我们需要重写它的getSupportedAnnotationTypesinitprocessgetSupportedSourceVersion方法。另外,为了能使用我们实现的这个注解处理器,,需要用一个服务文件来注册它,这里我们直接使用Google开源的AutoService就好,即在MyClassProcessor类添加@AutoService(Processor.class)注解。MyClassProcessor代码如下:

/** 注解处理器
 * author : jiangdg
 * date   : 2019/12/23 9:04
 * desc   :
 * version: 1.0
 */
@AutoService(Processor.class)
public class MyClassProcessor extends AbstractProcessor {
	
    // 1. 该方法用于初始化处理器,其中参数processingEnvironment是一个注解处理工具的集合。
    // 它包含众多工具类:
    //    Filer:创建、编写新的文件;
    //    Messager:打印信息
    //    Elements:是一个可以处理Element的工具类。
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    // 2. 该方法是注解处理器的核心方。返回true,表示注解由当前Processor处理。
    // 我们可以再这个方法中校验被注解的对象是否合法、扫描处理注解的代码
    // 以及自动生成需要的Java文件。其中,参数RoundEnvironment表示当前或是之前的运行环境,
    // 可以通过该对象查找找到的注解,并返回被注解注释的元素Element集合。
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Messager messager = processingEnv.getMessager();
        // 扫描所有被@BindView注解的元素
        Set<? extends Element> elements = 
            roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for(Element element : elements) {
            // 比如:判断元素是否为成员属性,如果是打印被注解的属性名称
           if(element.getKind() == ElementKind.FIELD) {
                messager.printMessage(Diagnostic.Kind.NOTE, "BindView'value=" +
                        element.toString());
            }
            ...
        }
        // 扫描所有被@BindView注解的元素
        Set<? extends Element> elements2 = 
            roundEnvironment.getElementsAnnotatedWith(BindString.class);
        for(Element element : elements2) {
            // 比如:判断元素是否为成员属性,如果是打印被注解的属性名称
           if(element.getKind() == ElementKind.FIELD) {
                messager.printMessage(Diagnostic.Kind.NOTE, "BindString'value=" +
                        element.toString());
            }
            ...
        }
        return true;
    }

    // 3. 该方法指定注解处理器是注册给哪些注解的
    //   返回一个Set集合,集合中指定要处理的注解类型名称(完整包名+类型)
    //   如"com.jiangdg.test.BindView",可以通过BindView.class.getCanonicalName()获取
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        annotations.add(BindString.class.getCanonicalName());
        return annotations;
    }

    // 4. 该方法指定使用的Java版本
    // 由于源文件编译成class文件主要借助PC端的javac工具,因此需要指定Java版本
    // 这里调用 SourceVersion.latestSupported()返回最新的Java版本即可
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

 这里我们重点理解下Element。Element是一个接口,表示一个程序的元素,它可以指代方法或者变量,由如下子接口表示:

ExecutableElement:表示某个类或接口的方法、构造方法或静态或实例,包括注释类型元素。
PackageElement : 表示一个包程序元素。
TypeElement:表示一个类或接口程序元素。
TypeParameterElement :表示一般类、接口、方法或构造方法元素的形式类型参数。
VariableElement : 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

 进一步来说,我们可以把Java类看作是一个结构化文件来理解Element,类似于XML或JSON文件。当然,我们来举个例子理解下,假如有一个Person类:

package com.jiangdg.test  // PackageElement
 
public class Person {     // TypeElement
	String name;          // VariableElement
	
	public Person(){}     // ExecutableElement
	
    public void read() {   // ExecutableElement
    	String type;          // VariableElement
    	type = "csdn blogs";
    	System.out.println("jiangdg is reading "+type);
    }
    
    public void swim(){ // ExecutableElement
    
    }
}

(3)最后,修改Java Library module的build.gradle以添加Auto Service依赖。

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 依赖annotations library
    implementation project(path: ':annotations')
    // 依赖AutoService插件
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
}
// 设置编码格式
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

至此,编译时注解处理器就实现了,当然(2)中并没有实现注解处理,这部分将在ButterKnife源码中学习。

1.2 依赖注入

 在学习依赖注入之前,我们需要了解什么是控制反转?所谓控制反转(Inversion of Control,缩写IoC)是软件专家Micheal Mattson提出一种面向对象编程的设计理论,这种理论又被称为"IoC理论",该理论提出的观点大致是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,其中这个“第三方”又被称为Ioc容器。IoC不是一种技术,只是一种思想,在引入IoC容器之前,传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合;有了IoC容器后,把创建和查找依赖对象的控制权交给了IoC容器,由IoC容器进行注入依赖对象,所以对象与对象之间是松散耦合,从而使得程序的整个体系结构变得非常灵活。**IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。由于这种“主从换位”的控制变换,因此被称作为控制反转。**下图描述了引入IoC容器的区别:
在这里插入图片描述
 依赖注入(Dependency Injection,缩写DI)实际上与控制反转(Ioc)是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler探讨了一个问题,控制反转是“哪些方面的控制被反转了 呢?最终他得出了答案:“获得依赖对象的过程被反转了。控制被反转之后,获得依赖对象的过程由自身管理变为由IoC容器主动注入。于是,他给控制反转取了一个更合适的名字,叫作依赖注入。所谓依赖注入,是指由IoC容器在运行期间,动态地将某种依赖关系注入到对象中。相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。依赖注入常见的三种实现方式:构造器注入、属性注入和接口注入,它们的区别如下:

  • 构造器注入
public class Car{
    // Engine是Car依赖的对象
    private Engine mEngine;
    public Car(Engine mEngine) {
        this.mEngine = mEngine;
    }
}
  • 属性注入(或称set注入)
public class Car{
    // Engine是Car依赖的对象
    private Engine mEngine;
    
    public void setEngine(Engine mEngine) {
        this.mEngine = mEngine;
    }
}
  • 接口注入
// 在接口中定义需要注入的信息
public interface ICar {
    public void setEngine(Engine mEngine)
}

public class Car implements ICar{
    // Engine是Car依赖的对象
    private Engine mEngine;
    
    @Override
    public void setEngine(Engine mEngine) {
        this.mEngine = mEngine;
    }
}

2. ButterKnife框架

ButterKnife由JakeWharton开源的一个专注于Android系统的View依赖注入框架,它通过注解的方式替换了使用findViewById来找到View的对象,从而大大简化了View对象的绑定代码,同时还支持属性变量值的绑定、各类事件的绑定。由于ButterKnife用到的注解不是在运行时反射的,而是通过注解处理器在编译时生成新的class(注:编译时注解),并完成相关的绑定工作,故对APP的运行性能不影响。此外,ButterKnife配置简单、使用方便,且拥有强大的View绑定和Click事件处理功能,因此能够简化代码和提升开发效率。
在这里插入图片描述

2.1 ButterKnife的基本使用

(1)在app的build.gradle中添加ButterKnife依赖

apply plugin: 'com.android.library'
// 在library module中使用才配置
apply plugin: 'com.jakewharton.butterknife'
    
android {
  ...
  // Butterknife requires Java 8.
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  implementation 'com.jakewharton:butterknife:10.2.1'
      
  // 如果是kotlin工程,需要将annotationProcessor改为kapt
  // 编译时注解处理器
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
}

 如果我们需要在library module中使用ButterKnife,除了在library module的build.gradle按上述配置,还需要在工程的根buid.gradle的buildscript中添加:

buildscript {
  repositories {
    mavenCentral()
    google()
   }
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.1'
  }
}

(2)在Activity中绑定ButterKnife,示例代码如下:

class ExampleActivity extends Activity {
  // 绑定View,替代findViewBy(id)
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;
  // 绑定资源,即对属性赋值
  // @BindAnim、@BindArray、@BindBool、@BindDimen、@BindFont
  // @BindDrawable、@BindFloat、@BindInt等
  @BindString(R.string.login_error) String loginErrorMessage;
  @BindView( R.id.imageView ) ImageView imageView ; 
  @BindBitmap( R2.mipmap.bm) Bitmap bitmap;
  @BindColor( R2.color.colorAccent) int black; 
      
  // 绑定点击事件
  // 注:支持绑定多个id事件,用法为@OnClick({R.id.submit1, R.id.submit2, R.id.submit3})
  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }
  
  // 除了点击事件,还支持绑定其他事件
  // @OnItemClick: item点击事件
  // @OnItemLongClick: item长按事件
  // @OnItemSelected: item选中事件
  // @OnLongClick: 长按事件
  // @OnTouch: 触摸事件
  // @OnTextChanged: 文本变化事件
  // @OnFocusChange: 焦点变化事件等

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    // 绑定Activity
    ButterKnife.bind(this);
      
    // TODO Use fields...
  }
}

(3)在Fragment中绑定ButterKnife。由于Fragment的生命周期不同于activity,在onCreateView中绑定一个Fragment时,需要在onDestroyView中将对其进行解绑,通过调用Unbinder的unbind方法。至于ButterKnife在Fragment中的使用与Activity中的使用一样。示例代码如下:

public class ButterknifeFragment extends Fragment{  
    private Unbinder unbinder;  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                             Bundle savedInstanceState) {  
        View view = inflater.inflate(R.layout.fragment, container, false);  
        // 返回一个Unbinder值(进行解绑)
        // 注意这里的this不能使用getActivity()  
        unbinder = ButterKnife.bind(this, view);  
        return view;  
    }  

    /** 
     * onDestroyView中进行解绑操作 
     */  
    @Override  
    public void onDestroyView() {  
        super.onDestroyView();  
        unbinder.unbind();  
    }  
} 

(4)在Adapter中绑定ButterKnife。在Adapter的ViewHolder中使用,将ViewHolder加一个构造方法,在new ViewHolder的时候把view传递进去。示例代码如下:

public class MyAdapter extends BaseAdapter {  

  @Override   
  public View getView(int position, View view, ViewGroup parent) {  
    ViewHolder holder;  
    if (view != null) {  
      holder = (ViewHolder) view.getTag();  
    } else {  
      view = inflater.inflate(R.layout.testlayout, parent, false);  
      holder = new ViewHolder(view);  
      view.setTag(holder);  
    }  

    holder.name.setText("Donkor");  
    holder.job.setText("Android");
    // etc...  
    return view;  
  }  

  static class ViewHolder {  
    @BindView(R.id.title) TextView name;  
    @BindView(R.id.job) TextView job;  

    public ViewHolder(View view) {  
      // 绑定Adapter
      ButterKnife.bind(this, view);  
    }  
  }  
} 

(5)添加代码混淆:

-keep class butterknife.** { *; }  
-dontwarn butterknife.internal.**  
-keep class **$$ViewBinder { *; }  

-keepclasseswithmembernames class * {  
    @butterknife.* <fields>;  
}  

-keepclasseswithmembernames class * {  
    @butterknife.* <methods>;  
}

2.2 ButterKnife原理解析

 ButterKnife项目结构与依赖图:
在这里插入图片描述
 其中,bufferknife-annotations项目实现各种注解类,比如BindView、Onclick等;butterknife-compiler项目实现注解解释器;bufferknife项目提供绑定Activity、Fragment以及事件监听器等;butter-gradle-plugin为gradle插件,剩余的项目均为辅助之用。

2.2.1 创建各类注解类

 ButterKnife框架的各种注解均在bufferknife-annotations项目中创建,这里我们以常用的@BindView@OnClick为例。@BindView源码如下:

// \butterknife-master10.2.0\butterknife-annotations\src\main\java\butterknife
// \BindView.java
@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

// androidx.annotation.IdRes
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface IdRes {
}

 从BuidView源码可知,它的保留策略为RUNTIME类型、作用域仅限字段,并且包含一个成员变量value,需要注意的是,这个成员变量又被@IdRes注解,该注解由androidx库提供,其作用是表示一个整形参数、字段或者方法返回值应该是一个id资源引用,如android.R.id.copy。也就是说,BindView注解的成员变量value的值应为一个id资源引用。接着,我们看@OnClick注解源码:

// \butterknife-master10.2.0\butterknife-annotations\src\main\java\butterknife
// \OnClick.java
@Target(METHOD)
@Retention(RUNTIME)
@ListenerClass(
    targetType = "android.view.View", 
    setter = "setOnClickListener",    
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}

  从OnClick源码可知,它的保留策略为RUNTIME类型、作用域仅限方法,并且包含一个成员变量value,这是一个整型数组,数组元素类型也为id资源引用。另外,OnClick注解又被@ListenerClass注解修饰,从它的定义可知,其只能用于修饰其他注解,且包含多个成员变量。@ListenerClass注解源码如下:

// \butterknife-master\butterknife-annotations\src\main\java\butterknife\internal
// \ ListenerClass.class
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)   // 只能修饰其他注解
public @interface ListenerClass {
  // 目标
  String targetType();
  // 目标的setter方法,该方法是设置监听器的	
  String setter();
  // 移除监听器的方法
  String remover() default "";
    
  String type();
  Class<? extends Enum<?>> callbacks() default NONE.class;
  // 监听器响应方法集合
  // ListenerMethod是一个注解
  // 它包含方法名称、参数、返回值类型以及返回值这四个成员变量
  ListenerMethod[] method() default { };
  enum NONE { }
}

2.2.2 ButterKnife的注解处理器

 前面我们说到,ButterKnife的注解处理器是在butterknife-compiler项目中实现的,该项目中有个ButterKnifeProcessor类,该类继承于AbstractProcessor,因此我们断定ButterKnifeProcessor类就是ButterKnife框架的注解处理器。接下来,我们就来分别看下它重写AbstractProcessor的几个方法。

  • init方法
// \butterknife-master\butterknife-compiler\src\main\java\butterknife\compiler
// \ButterKnifeProcessor.java
private Types typeUtils;
private Filer filer;
@Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    ...
    // 返回用来在类型上进行操作的某些实用工具方法的实现
    typeUtils = env.getTypeUtils();
    // 返回用来创建新源、类或辅助文件的 Filer
    filer = env.getFiler();
    ...
}

 该方法主要是完成一些初始化工具,比如利用processingEnvironment获取在类型上进行操作的工具类Types和用来创建源文件或辅助文件的Filer工具类。

  • getSupportedAnnotationTypes方法
// \butterknife-master\butterknife-compiler\src\main\java\butterknife\compiler
// \ButterKnifeProcessor.java
@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(BindAnim.class);
    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(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
}

private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
    OnCheckedChanged.class, //
    OnClick.class, //
    OnEditorAction.class, //
    OnFocusChange.class, //
    OnItemClick.class, //
    OnItemLongClick.class, //
    OnItemSelected.class, //
    OnLongClick.class, //
    OnPageChange.class, //
    OnTextChanged.class, //
    OnTouch.class //
);

 该方法用于表明当前注解处理器支持处理哪些注解,从它的实现可以看出,我们熟知的@BindView@BindStringOnClickOnLongClick等等均在列。

  • process方法
// \butterknife-master\butterknife-compiler\src\main\java\butterknife\compiler
// \ButterKnifeProcessor.java
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    // 1. 查找所有ButterKnife的注解来进行解析
    //   将收集的信息存储到bindingMap集合中
    //   注:TypeElement表示一个类或接口元素,并提供类或接口及其成员信息的访问;
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    
	// 2. 遍历 bindingMap,根据BindingSet得到一个JavaFile对象,然后输入java 类
    // 这个过程使用JavaPoet开源库实现,该提供了一种友好的方式来辅助生成java类的代码,
    // 同时将类代码生成文件。否则,需要自己拼接字符串来实现。
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();
		// 得到java类的代码
        JavaFile javaFile = binding.brewJava(sdk, debuggable);
        try {
            // 生成java文件
            javaFile.writeTo(filer);
        } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", 
                  typeElement, e.getMessage());
        }
    }

    return false;
}

BindingSet除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。

 process方法是注解处理器的核心,它主要完成目标类信息的收集(注释1),然后生成对应的java类(注释2)。接下来,我们重点分析目标类信息的收集过程,调用findAndParseTargets方法实现,源码如下:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    // 1. 创建两个集合,用于保存扫描得到的注解相关信息
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
    // 2. 完成注解信息的扫描收集,保存到builderMap集合中
    // TypeElement:代码使用某个注解的元素,如Activity、Dialog、Fragment等
    // BindingSet.Builder:创建BindingSet的建造器,其中BindingSet用于存储要生成类
    //                     的基本信息以及注解元素的相关信息。
    ...
    // (1)处理使用@BindString注解的所有元素
    // env.getElementsAnnotatedWith用于获取所有使用某个(如:BindView)注解的元素
    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceString(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindString.class, e);
      }
    }

    // Process each @BindView element.
    // (2)处理使用@BindView注解的所有元素
    // 由于@BindView只能注解成员变量,因此这里的element是VariableElment类型
    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);
      }
    }
    ...
    // 2. 对buiderMap和erasedTargetNames中的信息进行重新整理
    // 然后返回一个以TypeElement为key,BindingSet为vaule的Map集合
    Map<TypeElement, ClasspathBindingSet> classpathBindings =
                           findAllSupertypeBindings(builderMap, erasedTargetNames);
    // 使用builderMap.entrySet()创建一个队列
    // 队列中存储的是键值对Set集合
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    // 创建bindingMap集合
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
        // 从队列中取出一个元素(
        // key为TypeElement
        // value为BindingSet.Builder
        Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
        // TypeElement为当前类
        TypeElement type = entry.getKey();
        BindingSet.Builder builder = entry.getValue();
        // 查找当前类元素的父类元素
        TypeElement parentType = findParentType(type, erasedTargetNames, 
                                                classpathBindings.keySet());
        // 如果没有找到,则保存TypeElement和对应的BindingSet
        // 其中,BindingSet的创建通过建造者模式实现
        if (parentType == null) {
            bindingMap.put(type, builder.build());
        } else {
            BindingInformationProvider parentBinding = bindingMap.get(parentType);
            if (parentBinding == null) {
                parentBinding = classpathBindings.get(parentType);
            }
            // 如果找到父类元素,则给当前类元素对应的BindingSet.Builder设置父BindingSet
            // 然后再TypeElement和对应的BindingSet
            if (parentBinding != null) {
                builder.setParent(parentBinding);
                bindingMap.put(type, builder.build());
            } else {
                entries.addLast(entry);
            }
        }
    }

    return bindingMap;
}

 从findAndParseTargets方法源码可知,该方法的的作用首先是完成所有指定注解信息的扫描收集,并将解析的信息保存到builderMaperasedTargetNames两个集合中(注释1);然后在对这两个集合中的信息进行重新整理,最终返回一个以TypeElement为key,BindingSet为vaule的bindingMap集合,其中,TypeElement表示使用注解的类,比如Activity、Fragment、Dialog等,而BindingSet是bufferknife-compiler中的一个自定义类,用来存储要生成类的基本信息以及注解元素的相关信息注释2)。接下来,我们以@BindView注解为例,重点分析一下扫描解析并收集使用@BindView注解的某个元素相关信息,通过调用parseBindView方法实现。该方法的源码如下所示:

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,Set<TypeElement> erasedTargetNames) {
    // 1.由于@BindView只能注解成员变量,因此这里的element是VariableElment类型
    // 而element.getEnclosingElement()返回的是当前元素的父元素
	// 假如我们在Activity中使用@BindView注解,那么这里的enclosingElement指代Activity类
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // 2. 进行相关校验,不能用在private或static类型成员属性
    // isInaccessibleViaGeneratedCode()判断先判断当前元素的是否是private或static类型,
    //      再判断其父元素是否是一个类以及是否是private类型。
    // isBindingInWrongPackage()判断是否在系统相关的类中使用了ButteKnife注解
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", 
                      element)|| isBindingInWrongPackage(BindView.class, element);
    
    // 3. TypeMirror表示Java编程语言中的一种类型。 类型包括基元类型,声明的类型(类和接口类型),
    // 数组类 型,类型变量和空类型。还表示了通配符类型参数,可执行文件的签名和返回类型,
    // 以及与包和关键字void相对应的伪类型。
    TypeMirror elementType = element.asType();
    
    // 如果当前元素时类的成员变量
    // TypeVariable指代成员变量元素
    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注解时设置的属性值,即 View 对应的xml中的id
    int id = element.getAnnotation(BindView.class).value();
    // 尝试获取父元素对应的BindingSet.Builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    // 获取使用BindView注解成员属性资源ID,即android.R...
    Id resourceId = elementToId(element, BindView.class, id);
    // 如果BindingSet.Builder已经存在,则判断下当前元素的资源ID是否存储
    // 如果存在说明重复绑定,直接返回
    // 如果不存在,则直接将当前元素的资源ID存储到BindingSet
    // 注:一个BindingSet.Builder构建的BindingSet对应于一个目标类
    if (builder != null) {
        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 {
        // 3. 创建一个新的BindingSet.Builder对象builder并返回,
        // 并且以enclosingElement 为key,builder为value以键值对形式添加到builderMap中
        builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    // 判断当前元素是否使用了Nullable注解
    boolean required = isFieldRequired(element);
    // 创建一个FieldViewBinding,它包含了元素名、类型、是否是Nullable
    // 然后和元素资源id一同添加到BindingSet.Builder
    builder.addField(resourceId, new FieldViewBinding(name, type, required));

     // 记录当前元素的父类元素
    erasedTargetNames.add(enclosingElement);
}

 从parseBindView方法源码可知,该方法首先是进行校验,以确保@BindView注解不能用在修饰符为private或static类型的成员属性上,同时也不支持使用在系统类中(注释1);然后创建父元素的BindingSet.Builder对象builder,并以当前元素的父元素为key,builder对象为value以键值对的形式添加到builderMap集合中(注释2)。其中,builder对象将用于创建一个BindingSet对象,采用建造者模式实现。BindingSet.Builder是通过调用getOrCreateBindingBuilder方法实现的(注释3),源码如下:

private BindingSet.Builder getOrCreateBindingBuilder(
    Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    // 先判断enclosingElement对应的BindingSet.Builder是否已存在
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
        // 创建一个BindingSet.Builder
        builder = BindingSet.newBuilder(enclosingElement);
        // 添加到builderMap
        builderMap.put(enclosingElement, builder);
    }
    return builder;
}

接下来,我们再看BindingSet.newBuilder方法:

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类的名称
    // 假如我们在MainActivity中使用BindView
    // 那么这里的bindingClassName 为 MainActivity_ViewBinding
    ClassName bindingClassName = ClassName.get(packageName, className + 
                                               "_ViewBinding");
    // 判断父类元素是否为final类型
    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, 
                       isView, isActivity, isDialog);
}

BindingSet主要保存了要生成的目标类的基本特征信息,以及类中使用了 ButterKnife 注解的元素的信息,这样一个BindingSet就和一个使用了ButterKnife 的类对应了起来。

2.2.3 ButterKnife的bind方法

 通过对注解处理ButterKnifeProcessor处理过程的分析,我们容易知道处理器最终实现了根据注解的信息完成了被注解元素的查找和依赖注入(即根据资源ID找到view,并赋值给目标类的指定成员属性),并自动生成保存这些信息且以目标类_ViewBinding为命名的Java文件,但是此时我们在使用注解的视图类中对View控件的注入并不能用,这时候就需要调用ButterKnife的bind方法来使其生效,也可以称之为视图绑定,这个视图可以是Activity、Fragment、Dialoag等。由于针对不同的视图,bind方法有多个重载,这里就以在Activity中使用ButterKnife为例,它调用的是ButterKnife.bind(Activity target)方法,源码如下:

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

 该方法实现很简单,首先获取Activity的顶层视图DecorView,它继承于FrameLayout,归根结底继承于View,这个后面我们会用到。然后继续调用bind重载方法,该方法源码如下:

@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    // 1. 获取被绑定视图对应的Class对象
    // 这里假设以在MainActivity为例
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    // 2. 通过反射获取MainActivity_ViewBinding的构造器
    Constructor<? extends Unbinder> constructor = 
        findBindingConstructorForClass(targetClass);

    if (constructor == null) {
        return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
        // 3.调用MainActivity_ViewBinding的构造器
        // 实例化一个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);
    }
}

 在重载方法bind(target, sourceView)中将调用findBindingConstructorForClass方法通过反射的方法得到与目标Activity对应的xxActivity_ViewBinding类的构造器,再调用该构造器的newInstance方法完成对xxActivity_ViewBinding类一个实例的创建,从而完成视图绑定(使依赖注入生效),最后将这个实例返回。其中,findBindingConstructorForClass方法源码如下:

static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    // 1.从BINDINGS缓存中获取Class对象的构造器
    // 如果存在,则直接return,无需再次创建
    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;
    }
    // 2.获取Class对象的全限定名称(包名+类型)
    // 如果名称已android.或java.或androidx.开头,则直接返回null,查找失败
    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;
    }
    // 3.通过PathClassLoader类加载器
    // 加载名为clsName + "_ViewBinding"的类,假如这里的clsName="MainActivity"
    // 那么加载的是类MainActivity_ViewBinding
    try {
        Class<?> bindingClass = cls.getClassLoader()
                                .loadClass(clsName + "_ViewBinding");
        // 4.通过反射获取MainActivity_ViewBinding的构造器
        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;
}

 最后,我们再来看下MainActivity_ViewBinding的构造方法中是如何实现对View进行依赖注入的。前面说到,这些以xxx_ViewBinding命名的类都是注解处理器自动生成的,具体位于app/build/generated/ap_generated_sources/debug/out/目录下。其中,我们的MainActivity的代码:

/** 自定义Activity
 * author : jiangdg
 * date   : 2019/12/27 14:14
 * desc   :
 * version: 1.0
 */
public class MainActivity extends AppCompatActivity {
    @BindView(R.id.id_tv)
    TextView mTvTip;
    @BindView(R.id.id_btn)
    Button mBtn;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @OnClick({R.id.id_btn})
    public void onViewClick(View view) {

    }
}

MainActivity_ViewBinding.java源码:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f08004d;

  @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;
    // 找到id为R.id.id_tv,类型为TextView.class的对象
    // 将其赋值(注入)给MainActivity的mTvTip属性
    target.mTvTip = Utils.findRequiredViewAsType(source, R.id.id_tv, 
                                                 "field 'mTvTip'", TextView.class);
    view = Utils.findRequiredView(source, R.id.id_btn, 
                                  "field 'mBtn' and method 'onViewClick'");
    // 找到id为R.id.id_btn,Button.class的对象
    // 将其赋值(注入)给MainActivity的mBtn属性
    target.mBtn = Utils.castView(view, R.id.id_btn, 
                                 "field 'mBtn'", Button.class);
    view7f08004d = view;
    // 注册点击事件监听器
    // 调用MainActivity的onViewClick方法完成事件传递
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        // onViewClick方法即为被@OnClick注解的方法
        target.onViewClick(p0);
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    // 解绑视图,清理
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.mTvTip = null;
    target.mBtn = null;

    view7f08004d.setOnClickListener(null);
    view7f08004d = null;
  }
}

 在MainActivity_ViewBinding类的构造方法中,它会根据控件的资源ID获取对应的控件的实例,然后将其赋值(注入)给MainActivity类中的mTvTip和mBtn属性。其中,根据控件的资源ID获取对应的控件的实例通过调用findRequiredViewAsType方法实现,源码如下:

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
                                           Class<T> cls) {
    // 最终调用View的findViewById得到控件
    View view = findRequiredView(source, id, who);
    // cls为控件的Class对象,如TextView.class
    return castView(view, id, who, cls);
}

 该方法首先通过调用findRequiredView方法获取资源ID对应的View对象,然后再调用castView进行类型转换,比如资源ID为R.id.id_tv的是类型为TextView的View,那么则将其转换为TextView类型。其中,控件View的查找最终还是通过调用findViewById(id)方法实现的。findRequiredView方法等源码如下:

public static View findRequiredView(View source, @IdRes int id, String who) {
    // 调用View的findViewById得到控件
    View view = source.findViewById(id);
    if (view != null) {
        return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
                                    + name
                                    + "' with ID "
                                    + id
                                    + " for "
                                    + who
                                    + " was not found. If this view is optional add 
                                    '@Nullable' (fields) or '@Optional'"
                                    + " (methods) annotation.");
}

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
        // 类型转换
        return cls.cast(view);
    } catch (ClassCastException e) {
        String name = getResourceEntryName(view, id);
        throw new IllegalStateException("View '"
                                        + name
                                        + "' with ID "
                                        + id
                                        + " for "
                                        + who
                                        + " was of the wrong type. 
                                        + "See cause for more info.", e);
    }
}

 最后,我们再对ButterKnife框架的实现原理作个小结:ButterKnife从严格意义讲不算是依赖注入框架,它只是专注于Android系统的View注入框架,并不支持其他方法的注入,它可以减少大量的findViewById以及setOnClickListener代码,简化代码并提升开发效率。ButterKnife的核心是借助了annotationProcessor 和JavaPoet技术,其中annotationProcessor 注解处理器用于解析注解元素并收集要生成目标类的信息,并得到View的依赖注入的类源码;JavaPoet技术将实现将类代码生成文件,避免了我们自己去拼接字符串来实现。由于ButterKnife整个过程是在项目编译阶段完成的,因此对项目的运行时的性能没有影响。

发布了83 篇原创文章 · 获赞 293 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/103726082
今日推荐