862fd8f25b558d3273f358e6301f3d69
重学安卓:从 被误解 到 真香 的 Jetpack DataBinding!

温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。

截至目前,专栏已对 体系化文章 做了 930 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。

前言

很高兴见到你!

前面几期,我们在深度思考的帮助下,通过分析 背景状况身世使命,分别掌握了 Lifecycle、LiveData、ViewModel 的存在缘由、职责边界、工作目标。

相信阅读完这 3 篇的小伙伴,对 视图控制 之 状态管理标准化开发模式 的印象,已跑赢 90% 的 Andorid 开发者。😉

毫不夸张地说,在标准化开发模式的加持下,软件开发效率会 因为不可预期的错误被减少 90%,而超大幅度地提升。

而且只要确立了这样的开发模式,往后软件开发便可以不假思索地按部就班,省去了大量因模棱两可、犹豫不决而耽误的时间!

被不假思索地抵制的 DataBinding

原本 视图控制 的标准开发模式,到此为止就要宣告完结,

没想到在和一位 “曾任腾讯 QQ 8 年工龄核心员工 的 现任深圳南山某自动化代码生产工具” CEO 的技术攀谈中,在他再三推荐 DataBinding 后,我对 DataBinding 重新报以重视,

并且在真正地经过深度思考以后,才发现,DataBinding 不是我和许多人原本想当然以为的那样。

相信许多人对 DataBinding 的印象仅仅停留在:

一看 xml 中居然还要写 三元表达式 “这样的逻辑”,便草草地断定:DataBinding 不是个好东西,在声明式编程中书写 UI 逻辑,既不可调试,也不便于察觉和追踪,万一出现问题就麻烦了。

事实果真是这样吗?

文章目录一览

  • 前言
  • 被不假思索地抵制的 DataBinding
  • DataBinding 的目标只有三个
  • DataBinding 问世前的混沌世界
  • DataBinding 为什么能解决这三个问题?
  • 造成人们误认为 DataBinding 是在 xml 中写 UI 逻辑的原因
  • DataBinding 结合 LiveData 的使用
  • Note 2019.12.12 加餐
    • LiveData 和 ObservableField 的本质区别
  • Note 2020.10.21 加餐:
    • 对 BindingAdapter 存在缘由的解析
  • BindingAdapter 在解决 Drawable 复用问题上的绝妙使用
  • Note 2020.4.26 加餐
    • DataBinding 参数语法为何如此设计
  • Note 2020.5.6 加餐
    • BindingMethod 与 BindingAdapter 的本质区别
  • Note 2020.4.20 加餐
    • ViewBinding 和 DataBinding 不具可比性
  • DataBinding 的注意事项 和 屡试不爽的排错技巧
    • Note 2020.8.3 加餐:
    • 注意自定义视图包名的书写规范
  • Note 2020.7.27 加餐
    • 现有条件下解决 视图调用一致性问题 的最优解
  • 综上

DataBinding 的目标只有三个

同此前的所有文章一样,我写作的目的有且只有一个:帮助读者快速明确状况 —— 究竟是什么原因,导致某项技术的存在、某项技术究竟是为解决什么问题而存在。

所以本文也是一样开门见山 —— DataBinding 的存在,是为了实现唯一的核心目标:

通过 基于适配器模式 的 “数据驱动” 思想,规避视图调用的一致性问题

并且数据驱动 顺带免去了 因调用视图对象 而存在的大量冗余的判空处理。

并且数据驱动 顺带免去了 为调用视图对象 而存在的大量样板代码。

如果光是阅读了以上几点,你还是不太理解的话,那接下来我就来介绍 99% 的网文都不曾为你明确的真实状况。😉

DataBinding 问世前的混沌世界

例如 3 年前我 自主设计并开发的一款日记软件,该软件包含横、竖屏布局。

该 calendar 日历控件只存在于横屏布局中。

因此如果是以往的开发模式,我们需要在每个 Fragment 刷新 calendar 状态的地方,去逐个调用 calendar 控件,手动 set 新的状态,并且 set 前还要判断该控件存不存在。

那么这就造成一致性的问题:因为人类不是机器,人类一定会有疏忽的时候。特别是项目中包含数十个横竖屏不同布局方案、而每个页面又在多个方法中调用多个控件的情况,当后期项目改动,或换个同事接手时,很容易因疏忽了某些控件的存在与否,而埋下 “空指针异常” 的隐患

同理,为了拿到控件最终的状态,我们仍然是通过 调用控件的方式去获取状态,如此,若该控件不存在,可能造成空指针异常;而如果大量判空,代码就不优雅、十分冗余。

除了一致性问题,还有个很明显的问题,就是每新建一个页面,都需要为各种控件 findViewById —— 这些令人难以忍受的样板代码。

何况我从不使用 ButterKnife,因为它仅仅是为了解决样板代码,并且还需要额外地在代码中书写注解,看上去十分蹩脚。

那怎么办呢?

DataBinding 为什么能解决这三个问题?

首先,数据驱动 意味着,控件的状态被分离到 ViewModel 中管理,并且 ViewModel 这一层只需负责状态变量本身的变化,至于该变量在布局中究竟被哪些视图绑定、当前有没有视图来绑定,ViewModel 不用管。

那控件是如何做到被通知且更新状态的呢?

DataBinding 是通过 适配器模式和观察者模式 来管理控件刷新状态。

当状态变量发生变化时,需要开发者手动地完成状态的更新。这将通知 DataBinding 中绑定该变量的控件刷新数据。

在 BaseObservable 子类中,为 getter 赋予了 @Bindable 注解的,在编译后会生成与之相关的 BR 变量,于是 通过在 setter 中调用 notifyPropertyChanged 方法,可以通知 DataBinding 去刷新绑定该状态的控件

具体的代码可参考:在 Android 项目结构树状图中,通过 Java(Generate)节点 查看 目录下生成的 DataBindingImpl 文件(例如《重学安卓》项目生成的 FragmentTestDatabindingObserveBindingImpl),其中 executeBindings 方法,就是数据刷新的逻辑所在。

executeBindings 会依据 @Bindable 生成的 dirtyFlags 来区分当前是哪个 BR 被通知变化,从而只更新与之相关的控件,这么设计的缘由很容易理解:

1.为了 “精准投放”,而不是所有控件都重绘,从而维持良好的性能。

2.当多个控件绑定了同一个状态变量时,这些控件因为 dirtyFlags 一致的缘故,都会被通知,而无需开发者手动在 Java 代码中逐个调用控件去刷新。

但 Observable 的做法 反而滋生了另一种一致性问题:

例如状态变量对应的类中,某些成员要是遗漏了 notifyPropertyChanged,这个成员的通知刷新就会不起效。

所以说,数据驱动思想是好的,实现了视图与状态的分离。就是实现上有点不尽人意,居然需要开发者手动去通知。

那怎么办呢?于是 Google 又设计了一种 更为简便的状态声明方式

通过 ObservableField。

于是你只需在 ViewModel 中 分别声明该页面所包含的状态,然后在布局中与控件一对一地绑定就 OK 了。

如图,每个 ObservableField 都是持有 基本数据类型 或 String,来 作为单一的 State 去绑定 布局中对应控件的属性,从而当从外界为 ObservableField set 新的值、也即 state 发生变化时,能精准地只通知绑定了的控件发生重绘

再就是双向绑定,双向绑定的存在 主要是为了 避免直接调用控件实例来获取数据,来避免视图调用的一致性问题。

比如登录的场景,用户名、密码输入完毕点击 “提交”,那么在双向绑定的帮助下,我们只需通过 State-ViewModel 拿到与 用户名、密码 输入框发生双向绑定的 ObservableField 即可。

双向绑定的书写方式是在普通绑定的基础上加个等号,例如:

android:text="@={vm.name}"

所以,目前为止,相信大家在 Fragment 等视图控制器中对控件视图的判空,已经趋近于 0 了,因为数据驱动,完全不需要手动调用视图。

所以光是数据驱动,就已经解决了 findViewById 的问题、并且一口气把最开始提到的 3 个目标全解决了。😃

人们误认为 DataBinding 是在 xml 中写 UI 逻辑的原因

回到最初,文章开头提到的那种写法,是 官方文档 给出的具有误导性的 糟糕示例。

建议别这么写。状态值应尽可能地 直接反映预期结果,而不是作为条件在 xml 中发生逻辑判断。

并且值得再次强调的是,DataBinding 不负责 UI 逻辑,UI 逻辑原本在哪里写,现在还是在哪里写,只不过,UI 逻辑中原本调用控件实例去刷新状态的方式,现在改成了数据驱动,从而规避了“视图调用的一致性问题”。

DataBinding 的核心目标 就仅仅是解决这个问题而已。

简言之,DataBinding 在布局中的表达式,只是作为 UI 逻辑末端的 状态改变,而并不涉及错综复杂的 UI 逻辑本身。明确了 DataBinding 的 职责边界,无论 末端赋值 的花样再多,也不再会迷乱你双眼。

DataBinding 结合 LiveData 的使用

LiveData 是标准化开发中不可或缺的一员,通过 LiveData,可以让新手老手都轻易地写出遵循 “唯一可信源 数据分发” 的编码原则,从而避免 90% 不可预期的错误。

从 Android Studio 3.1 起已正式支持 LiveData DataBindingImpl 代码的生成。使用时就是简单地将 ViewModel 中的 ObservableField 替换为 MutableLiveData,并在视图控制器中为 DataBinding 绑定生命周期 Owner:mBinding.setLifecycleOwner(this); 就完事了。

top Created with Sketch.