【绝非标题党】JetPack之ViewBinding踩坑指南

1,127 阅读3分钟

一位程序员坐在电脑旁“ViewBinding踩坑”.png

前言


最近铺天盖地的文章,推荐使用ViewBinding。刚好最近在做android15的适配,也接入了ViewBinding 。要不是ButterKnife被禁用, 我都还不会使用ViewBinding. 可是在使用ViewBinding的过程中,也踩过了一些坑,在这里记录下来。


仅供自己学习, 如果能给你带来帮助,那实在是太好了。

目录


1. 视图绑定的发展史

2. ViewBinding的使用

3. ViewBinding踩坑

4. ViewBinding的原理

5. 参考文章


视图绑定的发展史

image.png

通过这张图能明显看出ViewBinding的优势了,这就是为什么谷歌主推ViewBinding了

大家一起来讨论一个问题:
虽然google推出了ViewBinding, 但是现在又推出了一个compose库, 未来会不会在某一天,compose直接取代了原先的命令式UI, 毕竟现在harmony都是采用声明式去写界面了, 以后就不存在xml写布局页面了


ViewBinding的使用

ViewBinding的接入流程,推荐大家还是看官网,官网毕竟写得详细。
官网ViewBinding的使用说明

  • ViewBinding的使用很简单,只需要在build.gradle中启用即可
android{
    ...
        viewBinding{
            enable = true
        }
    ...
}
  • 当启用ViewBinding后,AGP插件就会扫描res/layout里面的所有布局文件, 并生成对应的绑定类,其生成的格式会以驼峰的形式命名。比如有一个activity_main.xml文件,则生成的类名为:ActivityMainBinding

  • 生成的类文件的路径如下:

image.png


ViewBinding的踩坑
  • 首先给一段生成的ViewBinding的代码
public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;
  @NonNull
  public final TextView tv;

  @NonNull
  public final ViewStub viewStub;
  private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull TextView tv,
      @NonNull ViewStub viewStub) {
    this.rootView = rootView;
    this.tv = tv;
    this.viewStub = viewStub;

  @Override
  @NonNull
  public LinearLayout getRoot() {
    return rootView;

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    int id;
    missingId: {
      id = R.id.tv;
      TextView tv = ViewBindings.findChildViewById(rootView, id);
      if (tv == null) {
        break missingId;
      }

      id = R.id.view_stub;
      ViewStub viewStub = ViewBindings.findChildViewById(rootView, id);
      if (viewStub == null) {
        break missingId;
      }

      return new ActivityMainBinding((LinearLayout) rootView, tv, viewStub);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

通过上面生成的类可以看出,创建Binding实例提供了两个api

  • bind(View rootView) 这个RootView,必须是布局的根控件,如果不是,则会抛异常的

  • inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) 这个api有三个参数, attachToParent传值为true或false,这里面也有一个坑,先看一下我下面的这几种写法

//第一种:写法
val binding = ActivityMainBinding.inflate(LayoutInflater.from(context), container, true)

第二种写法: 
val view = LayoutInflater.from(context).inflate(R.layout.activity_main, container, true)
val binding = ActivityMainBinding.bind(view)

这里明确的告诉你, 第一种写法不会报错, 但是第二种写法会报错的。 具体原因是: attachRoot值为true, 通过分析源代码,可以得知返回的view是container, 而不是布局的根view, 这个时候调用bind方法传的不是布局的根控件,当然会报错了。

  • merge标签的使用也遇到了坑

若根控件为merge标签的布局文件,生成的binding类,inflate方法是只有两个参数的。

@NonNull
public static ActivityMainMergeBinding inflate(@NonNull LayoutInflater inflater,
    @NonNull ViewGroup parent) {
  if (parent == null) {
    throw new NullPointerException("parent");
  }
  inflater.inflate(R.layout.activity_main_merge, parent);
  return bind(parent);
}

ViewBinding的原理

学习任何一门技术,本着知其然,须知其所以然的目的。于是想探究ViewBinding这个类文件是如何生成的。

起初,我以为是通过APT的技术去生成的这个类文件的,后来仔细一想,这个注解都没有,怎么可能生成这个类的, 猜测肯定不是通过APT技术去实现的。

有哪位大佬知道AGP插件是通过什么技术,在扫描了布局文件后,怎么生成的类文件


参考文章

深入理解ViewBinding

♥️ ♥️ ♥️看到了这里的朋友,如果能给一个关注,评论,那真是太好了。感谢感谢♥️ ♥️ ♥️