ButterKnife注解原理分析

ButterKnife的优点

  • 强大的View绑定和Click事件处理功能,简化繁琐的代码编写
  • 可以支持Adapter中的VIewHolder绑定问题
  • 采用编译时通过注解生成代码,对运行时没有侵入,对比反射方式,效率倍高
  • 代码清晰,可读性强

核心技术

Java Annotation Processing是java中用于编译时扫描和解析Java注解的工具

在Java代码编译成Java字节码的时候就已经处理了@Bind、@OnClick等注解。

原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码。

ButterKnife框架工作流程

  1. 编译期间通过反射扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等;
    public class AnimationActivity extends AppCompatActivity {
        @BindView(R.id.listView)
        ListView listView;
        @BindView(R.id.animationImg)
        ImageView animationImg;
        @BindView(R.id.btn)
        Button btn;
    
        @OnClick(R.id.btn)
        public void onViewClicked() {
        }
    ...
    ...
    }
  2. 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会生成一个Java类,名字为<className>_ViewBinding
  3. 这个ViewBinding类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等
    public class AnimationActivity_ViewBinding implements Unbinder {
    
      private AnimationActivity target;
    
      private View view2131558495;
    
      @UiThread
      public AnimationActivity_ViewBinding(AnimationActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      @UiThread
      public AnimationActivity_ViewBinding(final AnimationActivity target, View source) {
        this.target = target; //构造方法传入了Activity的实例
    
        View view;
        //这种的调用形式表明Activity中BindView注解的属性,不能使用private修饰符
        target.listView = Utils.findRequiredViewAsType(source, R.id.listView, "field 'listView'", ListView.class);
        target.animationImg = Utils.findRequiredViewAsType(source, R.id.animationImg, "field 'animationImg'", ImageView.class);
        view = Utils.findRequiredView(source, R.id.btn, "field 'btn' and method 'onViewClicked'");
        target.btn = Utils.castView(view, R.id.btn, "field 'btn'", Button.class);
        view2131558495 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.onViewClicked();
          }
        });
      }
    
      @Override
      @CallSuper
      public void unbind() {
        AnimationActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.listView = null;
        target.animationImg = null;
        target.btn = null;
    
        view2131558495.setOnClickListener(null);
        view2131558495 = null;
      }
    }
  4. 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法

        ButterKnife会调用findViewBinderForClass(targetClass)加载MainActivity_ViewBinding.java类,然后动态注入MainActivity类中所有的View属性,如果Activity中有@OnClick注解的方法,ButterKnife会在ViewBinding类中给View设置onClickListener,并且将@OnClick注解的方法传入其中。

Q1、在MainActivity中的BindView注解的属性,不能使用private修饰符,那么使用protected修饰是否可以呢?

         可以的,因为ButterKnifeProcessor类的process()方法会在MainActivity的同一个包下生成BinderBinding类,所以同包下是可以调用到的。

Q2、为什么@Bind、@OnClick等注解标注的属性或方法必须是public或protected的?

      首先从使用方式上来讲,ButterKnife是通过Activity.属性或方法来注入View的;

      其次就是性能。如果你把View设置成private,那么框架必须通过反射来注入View,不管现在手机的CPU处理器变得多快,如果有些操作会影响性能,那么是肯定要避免的,这就是ButterKnife与其他注入框架的不同。

Q3、通过ButterKnife来注入View时,ButterKnife有bind(Object, View)和 bind(View)两个方法,有什么区别呢?

①、如果是自定义一个View,比如public class MyFragment extends Fragment,那就可以通过ButterKnife.bind(MyFragment)来注入View;

②、如果在一个ViewHolderinflate了一个xml布局文件,得到一个View对象,并且这个ViewLinearLayoutFrameLayout等系统自带View,那么不是不能用ButterKnife.bind(View)来注入View的,因为ButterKnife认为这些类的包名以com.android开头的类是没有注解功能的,所以这种情况你需要使用ButterKnife.bind(ViewHolder,View)来注入View。这表示是把@Bind、@OnClick等注解写到了这个ViewHolder类中,ViewHolder中的View呢?需要从后面那个View中去找。

Q4、ButterKnife对性能影响大吗?

       ButterKnife对性能影响不大,是因为它使用的APT(编译时解析技术),即注解类在编译时就通过反射的方式将注解扫描出来被编译成实际的类,使用时只需要调用bind()方法查找,所以应用性能不会受影响。

猜你喜欢

转载自blog.csdn.net/S_Alics/article/details/100652788
今日推荐