博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android开发——View Binding的使用与解析
阅读量:4045 次
发布时间:2019-05-24

本文共 6956 字,大约阅读时间需要 23 分钟。

1. 前言

  • 如何干掉模版代码是很多第三方框架的设计初衷,在Android开发中,findViewById()是必不可少的存在,这样的冗余代码在很久以前充斥在Android工程中,因此也出现了很多精简方案。
  • 在Android Studio3.6中加入了很多新特性——View Binding就是其中之一。目前已经在工程中使用并上线,未出现稳定性问题,因此做以下记录。
  • 在谈View Binding之前,我们先聊一下在此之前有哪些代表性方案做过这个事情,以及它们的优缺点分析

1.1 Butter Knife/Kotter Knife

Butter Knife框架是17年前后很火的存在。但是在Kotlin中直接使用ButterKnife的注解方式的话,会出现空指针的异常并导致绑定失败。从而Kotter Knife应运而生,可以理解成是Butter Knife的Kotlin版本。用法如下:

//Butter Knife@BindView(R.id.title) TextView title;ButterKnife.bind(this);// TODO Use fields...
// Kotter Knifeval submitButton: Button by bindView(R.id.submit_button)// TODO Use val...

然而这种方案已经被作者标记为Deprecated,作者认为该框架为每个视图引用分配了一个对象,这种思路不应该被采用,以及使用这种思路的框架也应该被淘汰,并推荐使用ViewBinding的方式。

1.2 Data Binding

依稀记得Data Binding是16年底的时候推出的,那时候真的很火,因为它是谷歌对于MVVM开发模式在Android上体现之一,其底层通过Annotation Processor实现的。用法如下,关键就是新增了data节点,以及在layout中的属性表达式@{}。

默认情况下,基于layout文件的名称会生成单词首字母大写并添加“Binding”后缀一个Binding类。此类包含layout属性以及在Views中的所有binding属性(例如user变量),并且能够为该属性赋值。因此在Java代码中:

MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);User user = new User("firstName", "lastName");binding.setUser(user);

但是Data Binding的劣势也很明显:

  • 它侵入了xml文件,因为只有布局文件的根标签必须是layout时, Data Binding才会生成对应的Binding Class。
  • Annotation Processor不仅对构建速度有负面影响,也会产生部分性能问题
  • 最最关键的,一些Data Binding相关的bug很难跟踪和解决,开发者在MVVM模式上的bug需要花费更多的时间,我觉得这也是MVVM没有在移动端发展起来的原因之一。

2. View Binding

这就是今天的主角了,View Binding既不会像Butter Knife那样为View分配多余的对象,也不会像Data Binding那样使用Annotation Processor,它的实现既简单又强大。首先来看一下如何使用View Binding。

2.1 View Binding所需环境

  • 首先需要将Android Studio升级到3.6版本及以上
  • 升级Gradle plugin版本到3.6.1
  • 并在app模块中手动开启view binding开关(View Binding以模块为粒度进行开启/关闭)
buildscript {
... dependencies {
classpath "com.android.tools.build:gradle:3.6.1" }}android {
... viewBinding {
enabled = true }}

这里升级gradle的时候遇到了一些小坑,即工程中使用的tinker版本过低,其使用了旧版gradle中的语法,因此会报错,将tinker版本升级到’1.9.14.6’后问题解决。

2.2 View Binding的使用

activity_main.xml的布局如下:

这里直接include了一个子布局,inner_layout.xml的布局如下:

  • 默认情况下,每一个布局xml文件都会生成一个对应的Binding类,名字的生成规则同Data Binding。
  • 在该例子中,会自动为我们生成一个ActivityMainBinding.java,以及include的标签对应的InnerLayoutBinding,这里include必须指定一个id
  • 当然,如果不需要为该xml生成Binding类,可以在xml的根布局中配置tools:viewBindingIgnore=“true”。
    在Java中的使用如下所示:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(LayoutInflater.from(this)); setContentView(binding.getRoot()); binding.firstButton.setText("Text Change"); binding.innerLayout.innerLayoutSecondButton.setText("Inner Layout Button Text Change"); }}

用法也很简单,直接通过binding点名字的方式就可以获取到控件实例,消除了findViewById的模版代码。

这里可以看到,setContentView的入参写法都变了,因为可通过XXXBinding类的getRoot函数获取到布局的根View,再通过setContentView添加到Activity。

2.3 View Binding的一些注意点

在2.2中我们演示了include的用法,只需要加一个id,我们就可以通过binding.innerLayout获取到该子布局。但是有个特殊情况需要处理,就是merge标签。

如果子布局inner_layout.xml中的布局如下,多了一个merge标签:

那么在activity_main.xml的布局的include标签中就一定不要设置id了(这里就不贴activity_main.xml的代码了),否则会找不到View报空指针异常。这个情况,我们可以先初始化主布局,再初始带merge的布局,那么就需要用到View Binding的另一个构造方法了,处理如下:

public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(LayoutInflater.from(this)); setContentView(binding.getRoot()); //使用 binding.firstButton.setText("Text Change"); //merge标签处理 InnerLayoutBinding subBinding = InnerLayoutBinding.bind(binding.getRoot()); subBinding.innerLayoutSecondButton.setText("Inner Layout Button Text Change"); }}

2.4 View Binding的原理解析

从2.2和2.3中我们看到了Binding类的两种构造方法,其实它有三种使用方法,如下所示:

inflate(@NonNull LayoutInflater inflater)inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent)bind(@NonNull View rootView)
  • 第一种和第二种直接调用inflate()方法,传入inflater即可得到Binding类的实例,它会在内部inflate对应的xml布局,并得到并持有该xml布局的根布局View实例,即例子中的binding.getRoot()。inflate()最后仍然会调用bind()。
  • 第三种方法就更简单粗暴了,直接使用bind()方法并传入View实例,这个View实例可以是你在外部自己生成的,即可为你生成一个Binding类的实例,再通过点view的方式,找到传入的根View下的子View实例。比如在我们的工程中,在Adapter中封装了ViewHolder加载xml的过程并将根布局view放在了ViewHolder的基类里,这种情况显然就只能使用bind(view)的方式了
  • 这里以2.2中生成的Binding类为例,该类的路径如下所示:
    在这里插入图片描述
  • 再看一下其生成的内容,分析一下它到底为我们做了什么事情:
// Generated by view binder compiler. Do not edit!public final class ActivityMainBinding implements ViewBinding {
@NonNull private final LinearLayout rootView; @NonNull public final Button firstButton; @NonNull public final InnerLayoutBinding innerLayout; private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull Button firstButton, @NonNull InnerLayoutBinding innerLayout) {
this.rootView = rootView; this.firstButton = firstButton; this.innerLayout = innerLayout; } @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) {
String missingId; missingId: {
//绑定View且做了非空判断,否则抛出空指针 Button firstButton = rootView.findViewById(R.id.first_button); if (firstButton == null) {
missingId = "firstButton"; break missingId; } View innerLayout = rootView.findViewById(R.id.inner_layout); if (innerLayout == null) {
missingId = "innerLayout"; break missingId; } InnerLayoutBinding innerLayoutBinding = InnerLayoutBinding.bind(innerLayout); return new ActivityMainBinding((LinearLayout) rootView, firstButton, innerLayoutBinding); } throw new NullPointerException("Missing required view with ID: ".concat(missingId)); }}
  • 可以看出View Binding的实现既简单又强大。它的主要逻辑在于bind()方法中,在绑定的过程做了非空判断。inflate()的逻辑也非常的清晰。

2.5 小彩蛋

  • 从Binding类的第43行代码开始,有一个很有意思的Java用法,叫做Java Label。形如string:{},并在代码块中满足条件时直接break string跳出指定的代码块,试想一下,如果不这样写,就要把throw new NullPointerException写N遍,可以学习一下这种写法。
block:{
if(condition) break block; // rest of code that won't be executed if condition is true}
  • 同时,Java Label也可以用在双层循环中,随意指定你想结束内层循环,还是外层循环。这里将break改为continue同样适用。
outterLoop: for(int i = 0; i < 10; i++){
while(condition) {
if(someConditon) break outterLoop; // 结束for循环 if(anotherConditon) break; // 结束while循环 // more code } // more code}

转载地址:http://zewci.baihongyu.com/

你可能感兴趣的文章
drat中构造方法
查看>>
JavaScript的一些基础-数据类型
查看>>
JavaScript基础知识(2)
查看>>
转载一个webview开车指南以及实际项目中的使用
查看>>
android中对于非属性动画的整理
查看>>
一个简单的TabLayout的使用
查看>>
ReactNative使用Redux例子
查看>>
Promise的基本使用
查看>>
coursesa课程 Python 3 programming 统计文件有多少单词
查看>>
coursesa课程 Python 3 programming 输出每一行句子的第三个单词
查看>>
Returning a value from a function
查看>>
coursesa课程 Python 3 programming Functions can call other functions 函数调用另一个函数
查看>>
coursesa课程 Python 3 programming The while Statement
查看>>
course_2_assessment_6
查看>>
coursesa课程 Python 3 programming course_2_assessment_7 多参数函数练习题
查看>>
coursesa课程 Python 3 programming course_2_assessment_8 sorted练习题
查看>>
在unity中建立最小的shader(Minimal Shader)
查看>>
1.3 Debugging of Shaders (调试着色器)
查看>>
关于phpcms中模块_tag.class.php中的pc_tag()方法的含义
查看>>
vsftp 配置具有匿名登录也有系统用户登录,系统用户有管理权限,匿名只有下载权限。
查看>>