ButterKnife源码解析之---源码分析

上篇文章中 我们看了ButterKnife的源码文档,看到了如何使用,那么这篇文章我们来分析具体原理。还是以activity中使用为例:

public class ExampleActivity extends Activity {
        //给需要初始化的view添加注解,参数为id
        @BindView(R.id.title)
        EditText titleView;
        @BindView(R.id.subtitle)
        EditText subtitleView;
 
        Override
        protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.example_activity);
          //绑定,而且要在setcontentview后面
          ButterKnife.bind(this);
       }
     }

ButterKnife.bind(this)方法 :

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    //获取根view
    View sourceView = target.getWindow().getDecorView();
    //调用重载方法
    return bind(target, sourceView);
  }

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    //获取view的Class类
    Class<?> targetClass = target.getClass();
    //根据Class去找一个构造器,什么构造器后面我们看
    Constructor<? extends Unbinder> constructor = 
    findBindingConstructorForClass(targetClass);
    //没找到,则返回null,说明失败了
    if (constructor == null) {
      return Unbinder.EMPTY;
    }
    //通过反射创建实例对象
      return constructor.newInstance(target, source);
  }

第一步:获取根view的Class

第二步:通过根View的Class找反射创建对象用的构造器

第三步:如果没找到,则返回EMPTY对象,找到了则通过反射创建对象

第二步是如何查找的:

@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //从解析缓存中查找,如果找到了直接返回
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      return bindingCtor;
    }
    //获取cls的全类名
    String clsName = cls.getName();
    //系统类直接跳过
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      return null;
    }
    try {
      //通过全类名+_ViewBinding去加载这个类
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //获取加载类的构造器
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    } catch (ClassNotFoundException e) {
      //没有找到则向父类查找
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      
    }
    //将解析过查找到的缓存起来
    BINDINGS.put(cls, bindingCtor);
    //返回构造器
    return bindingCtor;
  }

第一步:从全局缓存中查找,找到了直接返回

第二步:判断是不是系统类,如果是则直接返回null

第三步:主动加载类名为cla的全类名+_ViewBinding的类,并获取构造器

第四步:如果加载类或者获取构造器出错了,则向上查找

第五步:全局缓存解析查找结果,并返回反射构造器。也就是说第一次加载的时候会慢一些,但是第二次开始就比较快了。

通过上面五个步骤的分析,我们可以得出如下结论:

1.activity或者是fragment中,不需要解绑,不会发生内存泄漏。因为我们缓存解析结果都是用的Class类对象为key的。

2.系统类不可以使用这个框架(为什么,没想明白)

3.每个使用类都会有一个对应的cla的全类名+_ViewBinding的类存在,因为要创建实例。

至此,bind方法分析完了。可以我们并没有写cla的全类名+_ViewBinding的类啊,这个类是哪来的呢?这里我们就需要先补充个知识:编译期注解  就是通过注解实现编译期自动生成的java代码。生成的代码位置如下:

 下面我们就看看一个最简单的activity中使用和生成的文件:

public class TestActivity extends Activity {
        @BindView(R.id.base_refresh)
        SmartRefreshLayout smartRefreshLayout;

        @OnClick(R.id.base_refresh)
        void click(View view) {

        }
 
        Override
        protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.example_activity);
          //绑定,而且要在setcontentview后面
          ButterKnife.bind(this);
       }
     }
public class TestActivity_ViewBinding implements Unbinder {
    //目标activity
    private TestActivity target;
    //随意设置了属性名称 view7f0900b1 方便解绑时解耦
    private View view7f0900b1;

    @UiThread
    public TestActivity_ViewBinding(TestActivity target) {
        this(target, target.getWindow().getDecorView());
    }

    @UiThread
    public TestActivity_ViewBinding(final TestActivity target, View source) {
        this.target = target;

        View view;
        //就是 view=source.findViewById(R.id.base_refresh);
        view = Utils.findRequiredView(source, R.id.base_refresh, "field 'smartRefreshLayout' and method 'click'");
        //就是类型强转  target.smartRefreshLayout=(SmartRefreshLayout)view;
        target.smartRefreshLayout = Utils.castView(view, R.id.base_refresh, "field 'smartRefreshLayout'", SmartRefreshLayout.class);
        view7f0900b1 = view;
        //设置点击事件 DebouncingOnClickListener 这个类还可以防止快速连续点击
        view.setOnClickListener(new DebouncingOnClickListener() {
            @Override
            public void doClick(View p0) {
                //调用我们写的点击方法
                target.click(p0);
            }
        });
    }

    @Override
    @CallSuper
    public void unbind() {
        //解耦操作
        TestActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;

        target.smartRefreshLayout = null;

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

解释都在上面了。其实内部就是帮我们实现了findviewbyid和设置点击事件。通过上面分析我们可以得出:

1.每个点击注解都会帮我们实现点击事件,创建一个对象,所以其实不是很好,不过可以忽略

2.虽然我们在使用中不主动调用unbind方法不会发生内存泄漏,但是最好还是不用的时候调用一下。

至此源码分析完毕。

那么我们能学到什么?

1.最重要的就是学到了编译期注解的实际应用案例,以后可以参考这个例子来写我们自己的编译期注解代码,自动帮我们解决一些重复而简单的工作。

2.源码中还有一个防止快速点击的代码,我觉得也很值得学习记录一下:

public abstract class DebouncingOnClickListener implements View.OnClickListener {
  static boolean enabled = true;

  private static final Runnable ENABLE_AGAIN = () -> enabled = true;

  @Override public final void onClick(View v) {
    if (enabled) {
      enabled = false;
      v.post(ENABLE_AGAIN);
      doClick(v);
    }
  }

  public abstract void doClick(View v);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值