您现在的位置是:首页 >技术杂谈 >移动架构47_视图绑定组件ViewBinding网站首页技术杂谈

移动架构47_视图绑定组件ViewBinding

鱼包子Ray 2023-06-19 20:00:02
简介移动架构47_视图绑定组件ViewBinding

Android移动架构汇总​​​​​​​

一、控件的声明

在Activity中绑定布局中的控件一般有三种实现方式:

  • 第一种用最原生态的findViewById方法来绑定
  • 第二种方式可以使用ButterKnife开源框架实现,ButterKnife对组件化的支持却很不友好
  • 第三种方式是使用Kotlin的扩展插件来获取视图控件,使用局限性:无法跨模块操作,类型不安全:不同的资源文件可以存在相同的控件id,因此在View层存在引用id来源出错的问题。

Kotlin 1.4版本中废弃了扩展插件,Google推荐使用ViewBinding来替代废弃的扩展插件

二、ViewBinding的基本使用

build.gradle中添加如下配置:

android {
	...
    viewBinding {
        enabled = true
    }
	...
}

配置完成后,系统会为该模块中的每个XML布局文件生成一个绑定类,这个绑定类的命名就是XML文件的名称转换为驼峰式,并在末尾添加“Binding”一词,直接可以通过view binding这一中间类获取到xml中定义的view组件了

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        // setContentView(R.layout.activity_new_stock_bjs_records);
      viewBinding = ActivityNewStockBjsRecordsBinding.inflate(getLayoutInflater());
    
      viewBinding.navigationBar.setText("北交所新股申购")
    }

需要注意的一点是,开发者需要在onDestroyView()方法中将绑定类实例赋值为null。Fragment的存在时间比其视图时间长,所以开发者需要在onDestroyView()方法中清除对绑定类实例的所有引用,否则可能存在内存泄漏的风险

注意
如果某个布局文件不需要的话,可以通过tools:viewBinding-Ignore=“true”属性来设置

三、ViewBinding特点

  1. 集成简单
  2. 代码简洁,维护容易
  3. 对象空值安全:由于视图绑定会对视图直接引用,因此不存在因视图id无效而引发空指针异常的风险。
  4. 类型安全:每个绑定类中的字段均具有与它们在xml文件中引用的视图相匹配的类型,因此不存在强制转换可能导致的异常问题

四、ViewBinding的封装

ViewBinding组件的使用流程基本是固定的,主要分为三步:

  1. 调用生成的绑定类中的inflate()方法来获取绑定类的实例。
  2. 通过调用绑定类的getRoot()方法获取对根视图。
  3. 将根视图传递到setContentView()中,并与当前Activity绑定。
    由于ViewBinding使用的流程是固定的,因此在基础业务的开发中,经常会定义一个BaseActivity处理所有Activity的相同业务逻辑,这时,就可以将这部分逻辑封装在BaseActivity中
/**
 * 使用ViewBinding的基础类
 *
 * @param <T>
 */
public abstract class ViewBindingBaseActivity<T extends ViewBinding> extends ActivityBase {

    protected T mViewBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mViewBinding = getViewBinding();
        setContentView(mViewBinding.getRoot());
    }

    /**
     * @return 返回Activity对于xml生成的ViewBinding对象
     */
     public abstract T getViewBinding();

}

五、源码

在项目模块的build.gradle中开启ViewBinding功能之后,若进行项目编译,就会扫描layout下所有的布局文件,并生成对应的绑定类(Project视图:
app/build/generated/data_binding_base_class_source_out/debug/out/包名/databinding路径下)。这一点是由gradle插件实现的

public final class ActivityTestBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;

  @NonNull
  public final ImageView imShezhi;

  @NonNull
  public final HomeRefresh reRefresh;

  @NonNull
  public final RecyclerView rv;

  @NonNull
  public final TextView tvName;

  private ActivityTestBinding(@NonNull LinearLayout rootView, @NonNull ImageView imShezhi,
      @NonNull HomeRefresh reRefresh, @NonNull RecyclerView rv, @NonNull TextView tvName) {
    this.rootView = rootView;
    this.imShezhi = imShezhi;
    this.reRefresh = reRefresh;
    this.rv = rv;
    this.tvName = tvName;
  }

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

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

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

  @NonNull
  public static ActivityTestBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    String missingId;
    missingId: {
      ImageView imShezhi = rootView.findViewById(R.id.im_shezhi);
      if (imShezhi == null) {
        missingId = "imShezhi";
        break missingId;
      }
      HomeRefresh reRefresh = rootView.findViewById(R.id.re_refresh);
      if (reRefresh == null) {
        missingId = "reRefresh";
        break missingId;
      }
      RecyclerView rv = rootView.findViewById(R.id.rv);
      if (rv == null) {
        missingId = "rv";
        break missingId;
      }
      TextView tvName = rootView.findViewById(R.id.tv_name);
      if (tvName == null) {
        missingId = "tvName";
        break missingId;
      }
      return new ActivityTestBinding((LinearLayout) rootView, imShezhi, reRefresh, rv, tvName);
    }
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

从生成的ActivityTestBinding文件中可以轻松地看出,在调用了inflate之后会调用bind方法,而bind方法依然是通过findViewById绑定

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。