ButterKnife原理分析

官方传送门

本文基于compile 'com.jakewharton:butterknife:8.6.0'分析

1.原理

相信我们做Android都用过或了解过ButterKnife,毕竟也是github上星星过万的库。说ButterKnife原理之前,一般这样的注入框架(比如xUtils)是运行时注解,声明注解的Retention为Runtime,即注解的保留时期为运行时,然后通过反射来完成view的绑定,这样比较耗费资源,影响性能。而ButterKnife它是基于编译时的注解,即注解的Retention为Class,利用Java Annotation Processor技术自定义注解,注册注解处理器,然后生成相应的辅助类,在运行时,通过bind方法完成绑定。

2.引入

1.在项目中引入
在module下的build.gradle的dependencies中引入

dependencies {
  compile 'com.jakewharton:butterknife:8.6.0'
  annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
}

2.在library中引入
将插件加入到buildscript

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

在module下的build.gradle中apply plugin

apply plugin: 'com.jakewharton.butterknife'

3.基本用法

直接看代码

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv_text)
    TextView mTvText;

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

    @OnClick(R.id.tv_text)
    protected void onClick() {
        mTvText.setText("ButterKnife");
    }
}

这里就完成了我们经常写的findViewById以及setOnclickListener的工作。

PS:Butter Knife如果是在library中使用,需要将R转为R2,原因是在library里面由于R类中变量不再是final类型而导致各种问题。

4.源码分析

1.先看我们在入口时的bind方法,返回了一个Unbinder对象

 @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }
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());
    Constructor constructor = findBindingConstructorForClass(targetClass);

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

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      // 创建了一个Unbinder对象,并在构造方法中完成了初始化
      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);
    }
  }

2.再看findBindingConstructorForClass方法,它返回了我们绑定目标类的一个构造器

@Nullable @CheckResult @UiThread
  private static Constructor findBindingConstructorForClass(Class cls) {
    // 缓存中取
    Constructor bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    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 {
      // 这里获得了一个clsName + "_ViewBinding"的class
      Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      // 获得构造器
      bindingCtor = (Constructor) 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;
}

这里如果我们是在Activity中bind的话,那么loadClass里的name就是"Activity_Viewbinding",这个类是怎么来的呢?就是我们提到的Java Annotation Processor,在编译时生产了这样的一个类。

3.我们看下在build下的classes里确实有一个MainActivity_ViewBinding.class

ButterKnife原理分析_第1张图片
Activity_ViewBinding.png

再看下这个类里面到底干了什么,是我们非常熟悉的findViewByIdsetOnClickListener方法

public class MainActivity_ViewBinding implements Unbinder {
    private MainActivity target;
    private View view2131427422;

    @UiThread
    public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
    }

    @UiThread
    public MainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
        // 这里就是调用了source.findViewById,完成了View的初始化
        View view = Utils.findRequiredView(source, 2131427422, "field \'mTvText\' and method \'onClick\'");
        // 给我们在MainActivity中@BindView的mTvText 赋值,这里mTvText就成功被初始化了
        target.mTvText = (TextView)Utils.castView(view, 2131427422, "field \'mTvText\'", TextView.class);
        this.view2131427422 = view;
        // 这里
        view.setOnClickListener(new DebouncingOnClickListener() {
            public void doClick(View p0) {
                target.onClick();
            }
        });
    }

    @CallSuper
    public void unbind() {
        MainActivity target = this.target;
        if(target == null) {
            throw new IllegalStateException("Bindings already cleared.");
        } else {
            this.target = null;
            target.mTvText = null;
            this.view2131427422.setOnClickListener((OnClickListener)null);
            this.view2131427422 = null;
        }
    }
}

MainActivity_ViewBinding它实现了Unbinder接口,在构造方法中进行了我们的findViewById以及setOnClickListener等操作。

4.MainActivity_ViewBinding这个类是怎么生成的呢?我们来看一下ButterKnifeProcessor这个类,它继承了AbstractProcessor
它的init方法

@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) {
    }
  }

ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。Types是用来处理TypeMirror的工具类,Filer用来创建生成辅助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运行的时候,会扫描所有的Java源文件,然后每一个Java源文件的每一个部分都是一个Element,比如一个包、类或者方法。

再看这个类中最重要的process方法

@Override public boolean process(Set elements, RoundEnvironment env) {
    Map bindingMap = findAndParseTargets(env);

    for (Map.Entry entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk);
      try {
        // 生成文件到apt目录下,也就是我们上面提到的MainActivity_ViewBinding类
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

这个方法的作用主要是扫描、处理我们程序中的注解,BindingSet通过JavaPoet的Api构建Java类,最后通过Filer写入到文件中,完成了上面提到的辅助类的生成。

5.总结

ButterKnife的依赖注入其实就是通过ButterKnifeProcessor这个注解处理器生成辅助类,然后在使用的地方调用bind方法时,就会通过反射创建出辅助类对象,在辅助类的构造方法中完成View的初始化等操作。

你可能感兴趣的:(ButterKnife原理分析)