一、使用方法

  1、添加依赖。

  

  1. implementation 'com.jakewharton:butterknife:8.8.1'
  2. annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

  2、使用。

  1. public class MainActivity extends AppCompatActivity {
  2. // 1、控件id绑定
  3. @BindView(R.id.myBtn)
  4. Button myBtn;
  5.  
  6. Unbinder unbinder = null;
  7. // 2、点击事件绑定
  8. @OnClick(R.id.myBtn)
  9. public void click() {
  10. Toast.makeText(this,"btn click",Toast.LENGTH_SHORT).show();
  11. myBtn.setText("hello world");
  12. }
  13.  
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. //3、activity注册
  19. unbinder = ButterKnife.bind(this);
  20. }
  21.  
  22. @Override
  23. protected void onDestroy() {
  24. //4、activity 取消注册
  25. unbinder.unbind();
  26. super.onDestroy();
  27. }
  28. }

  3、编译运行。

二、原理解析

  很明显的我们可以看出,ButterKnife.bind(this)   是 activity和ButterKnife建立关系的地方,我们从这里入手分析。

----->>点击进入    bind

  1. public static Unbinder bind(@NonNull Activity target) {
  2. //获取decorView 就是页面的跟布局View 本质 是FrameLayout
  3. View sourceView = target.getWindow().getDecorView();
  4. // 获取unbinder
  5. return createBinding(target, sourceView);
  6. }

---->> 点击进入  createBinding

  1. private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
  2. Class<?> targetClass = target.getClass();
  3. if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
  4. Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
  5.  
  6. if (constructor == null) {
  7. return Unbinder.EMPTY;
  8. }
  9.  
  10. //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
  11. try {
  12. return constructor.newInstance(target, source);
  13. } catch (IllegalAccessException e) {
  14. throw new RuntimeException("Unable to invoke " + constructor, e);
  15. } catch (InstantiationException e) {
  16. throw new RuntimeException("Unable to invoke " + constructor, e);
  17. } catch (InvocationTargetException e) {
  18. Throwable cause = e.getCause();
  19. if (cause instanceof RuntimeException) {
  20. throw (RuntimeException) cause;
  21. }
  22. if (cause instanceof Error) {
  23. throw (Error) cause;
  24. }
  25. throw new RuntimeException("Unable to create binding instance.", cause);
  26. }
  27. }

主要的过程就是

生成constructor 先findBindingConstructorForClass

  如果找不到就 返回  Unbinder.EMPTY,这里的Unbinder.EMPTY就是new Unbinder() 然后直接结束函数 ,不做处理,

  如果得到constructor就 调用constructor.newInstance得到一个unbinder返回,我们稍微看一下newInstance 返回的是一个泛型,至于是在何时传入的泛型,我们先保留下来。

  1. public T newInstance(Object ... initargs)
  2. throws InstantiationException, IllegalAccessException,
  3. IllegalArgumentException, InvocationTargetException
  4. {
  5. if (serializationClass == null) {
  6. return newInstance0(initargs);
  7. } else {
  8. return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
  9. }
  10. }

我们继续探查他是如何得到construtor的

---->>点击进入 findBindingConstructorForClass

  1. @Nullable @CheckResult @UiThread
  2. private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  3. // 1、先从BINDINGS 里边获取construtor
  4. Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
  5. if (bindingCtor != null) {
  6. if (debug) Log.d(TAG, "HIT: Cached in binding map.");
  7. return bindingCtor;
  8. }
  9.  
  10. String clsName = cls.getName();
  11. if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
  12. if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
  13. return null;
  14. }
  15. try {
  16.  // 2、如果没有就,利用反射生成construtor
  17. Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
  18. //noinspection unchecked
  19. bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
  20. if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
  21. } catch (ClassNotFoundException e) {
  22. if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
  23. bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
  24. } catch (NoSuchMethodException e) {
  25. throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
  26. }
  27. // 3、将生成的construtor 放入BINDINGS做备份
  28. BINDINGS.put(cls, bindingCtor);
  29. return bindingCtor;
  30. }

我们重点看,生成construtor这一段:

String clsName = cls.getName();

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);

  1、这里向Contrutor传入Unbinder 泛型,这就能够解释newInstance的返回值是Unbinder。

  2、这里泛型使用的类名(clsName + "_ViewBinding")中,竟然包含了我们传进来的类名,这里完整的类名就是MainActivity_ViewBinding   ,能够反射获取实例,说明这个类是确实存在的,二我们并没有编写相关的类,而在app运行过程更不可能产生类,那就只能是,app运行之前 由ButterKnife 生成的。

我们不妨看看这个类里有什么:

  1. public class MainActivity_ViewBinding implements Unbinder {
  2. private MainActivity target;
  3. private View view2131165258;
  4. @UiThread
  5. public MainActivity_ViewBinding(MainActivity target) {
  6. this(target, target.getWindow().getDecorView());
  7. }
  8. @UiThread
  9. public MainActivity_ViewBinding(final MainActivity target, View source) {
  10. this.target = target;
  11. View view;
  12. view = Utils.findRequiredView(source, R.id.myBtn, "field 'myBtn' and method 'click'");
  13. target.myBtn = Utils.castView(view, R.id.myBtn, "field 'myBtn'", Button.class);
  14. view2131165258 = view;
  15. view.setOnClickListener(new DebouncingOnClickListener() {
  16. @Override
  17. public void doClick(View p0) {
  18. target.click();
  19. }
  20. });
  21. }
  22. @Override
  23. @CallSuper
  24. public void unbind() {
  25. MainActivity target = this.target;
  26. if (target == null) throw new IllegalStateException("Bindings already cleared.");
  27. this.target = null;
  28. target.myBtn = null;
  29. view2131165258.setOnClickListener(null);
  30. view2131165258 = null;
  31. }
  32. }

从代码中我们看到了 我们view的id以及activity中的变量名,可以联想到,是我们添加Bind注解时传进来的。

---->>findRequiredView我们可以猜测出是 寻找控件使用的。我们可以看一看, 

  1. public static View findRequiredView(View source, @IdRes int id, String who) {
  2. View view = source.findViewById(id);
  3. if (view != null) {
  4. return view;
  5. }
  6. String name = getResourceEntryName(source, id);
  7. throw new IllegalStateException("Required view '"
  8. .......
  9. }

    我们终于看到了  findViewById。

---->>  castView  传入 view  传入 class  ,很明显是转型使用的。

  还有一个问题就是ButterKnife 如何能够在运行前根据我们的代码 ,生成相应的  _ViewBinding  文件的,请继续看。

三、注解处理器

  在添加依赖时我们还添加了一个,annotationProcessor,就是完成呢些文件生成的。

  1.  
  1. annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
  1.    这其中涉及到了annotationProcessor技术,和 APT(Annotation Processing Tool)技术,他是一种注解处理器,
    在项目编译期可以对源代码进行扫描,找出存活时间为RetentionPolicy.CLASS的指定注解,然后对注解进行解析处理。
      至于后边java类的生成,涉及到了JavaPoet技术

  这里我们先看用注解处理器收集类信息的过程,之前我们已经在app的 build.gradle引入了 ButterKnife 的注解处理器: butterknife-compiler,其中有一个ButterKnifeProcessor 类完成了注解处理器的核心逻辑。

  1. @AutoService(Processor.class)
  2. public final class ButterKnifeProcessor extends AbstractProcessor {
  3.  
  4. @Override
  5. public synchronized void init(ProcessingEnvironment env) {
  6. super.init(env);
  7. String sdk = env.getOptions().get(OPTION_SDK_INT);
  8. ......
  9. debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
  10. elementUtils = env.getElementUtils();
  11. typeUtils = env.getTypeUtils();
  12. filer = env.getFiler();
  13. try {
  14. trees = Trees.instance(processingEnv);
  15. } catch (IllegalArgumentException ignored) {
  16. }
  17. }
  18.  
  19. @Override
  20. public Set<String> getSupportedOptions() {
  21. return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
  22. }
  23.  
  24. @Override
  25. public Set<String> getSupportedAnnotationTypes() {
  26. Set<String> types = new LinkedHashSet<>();
  27. for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
  28. types.add(annotation.getCanonicalName());
  29. }
  30. return types;
  31. }
  32.  
  33. @Override
  34. public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
  35. Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
  36.  
  37. for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
  38. TypeElement typeElement = entry.getKey();
  39. BindingSet binding = entry.getValue();
  40.  
  41. JavaFile javaFile = binding.brewJava(sdk, debuggable);
  42. try {
  43. javaFile.writeTo(filer);
  44. } catch (IOException e) {
  45. error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
  46. }
  47. }
  48. return false;
  49. }
  50.  
  51. @Override
  52. public SourceVersion getSupportedSourceVersion() {
  53. return SourceVersion.latestSupported();
  54. }
  55. }

注意,ButterKnifeProcessor类上使用了@AutoService(Processor.class)注解,来实现注解处理器的注册,注册到 javac 后,在项目编译时就能执行注解处理器了。

ButterKnifeProcessor继承了AbstractProcessor抽象类,并重写以上五个方法,如果我们自定义解处理器也是类似的,看下这几个方法:

1、init()

首先 init() 方法完成sdk版本的判断以及相关帮助类的初始化,帮助类主要有以下几个:

  • Elements elementUtils,注解处理器运行扫描源文件时,以获取元素(Element)相关的信息。Element 有以下几个子类:
    包(PackageElement)、类(TypeElement)、成员变量(VariableElement)、方法(ExecutableElement)
  • Types typeUtils,
  • Filer filer,用来生成 java 类文件。
  • Trees trees,
2、getSupportedAnnotationTypes()

该方法返回一个Set<String>,代表ButterKnifeProcessor要处理的注解类的名称集合,即 ButterKnife 支持的注解:butterknife-annotations

3、getSupportedSourceVersion()

返回当前系统支持的 java 版本。

4、getSupportedOptions()

返回注解处理器可处理的注解操作。

5、process()

最后,process() 方法是我们要重点分析的,在这里完成了目标类信息的收集并生成对应 java 类。


ButterKnife 原理解析的更多相关文章

  1. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  2. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  3. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  4. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  5. GeoHash原理解析

    GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...

  6. alibaba-dexposed 原理解析

    alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...

  7. 支付宝Andfix 原理解析

    支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...

  8. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

  9. Request 接收参数乱码原理解析三:实例分析

    通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...

随机推荐

  1. CDN原理实现详情

    CDN真是个好东西,但是究竟是怎么实现的呢, 学习下吧 首先浏览器发起请求 Dns解析寻找服务器资源 使用CDN加速的内容会被放到不同的服务器上 根据用户的请求来判断 -- 算了表达不清楚,看图吧! ...

  2. HTML to PDF pechkin

    1. Goto Nuget 下载 Pechkin 控件 2. 创建需要打印的的PDF controller 和 Action, 这里会调用其他页面的内容进行打印. public ActionResul ...

  3. 使用eclipse开发hbase程序

      一:在eclipse创建一个普通的java项目 二:新建一个文件夹,把hbase需要的jar放进去,我这里把hbase/lib/*.jar 下所有的jar都放进去了,最后发现就用到了下面三个jar ...

  4. linux 参数内核

    优化Linux内核参数   转自:http://www.centoscn.com/CentOS/config/2013/0804/992.html vim /etc/sysctl.conf 1.net ...

  5. shiro设置session超时

    通过api:Shiro的Session接口有一个setTimeout()方法 //登录后,可以用如下方式取得session SecurityUtils.getSubject().getSession( ...

  6. erlang 爬虫——爬取网页图片

    说起爬虫,大家第一印象就是想到了python来做爬虫.其实,服务端语言好些都可以来实现这个东东. 在我们日常上网浏览网页的时候,经常会看到一些好看的图片,我们就希望把这些图片保存下载,或者用户用来做桌 ...

  7. 【转】使用 Python Mock 类进行单元测试

    出处:https://www.oschina.net/translate/unit-testing-with-the-python-mock-class?lang=chs&page=2#

  8. iOS 线程管理的学习记录

    本文转载至 http://www.2cto.com/kf/201312/265451.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2 ...

  9. PHP手机号码正则表达式

    php用正则表达式判断手机号码的写法:从文章中匹配出所有的手机号就可以preg_match_all(),如果要检查用户输入的手机号是否正确可这样来检查:preg_match(). 用正则匹配手机号码的 ...

  10. 【BZOJ3993】[SDOI2015]星际战争 二分+最大流

    [BZOJ3993][SDOI2015]星际战争 Description 3333年,在银河系的某星球上,X军团和Y军团正在激烈地作战.在战斗的某一阶段,Y军团一共派遣了N个巨型机器人进攻X军团的阵地 ...