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

前言

很高兴见到你!

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

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

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

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

被不假思索地抵制的 DataBinding

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

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

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

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

android:visibility="@{ age > 13 ? View.VISIBLE : View.GONE }"

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

事实果真是这样吗?

文章目录一览

  • 前言
  • 被不假思索地抵制的 DataBinding
  • DataBinding 的目标只有三个
  • DataBinding 问世前的混沌世界
  • DataBinding 为什么能解决这三个问题?
  • 造成人们误认为 DataBinding 是在 xml 中写 UI 逻辑的原因
  • DataBinding 结合 LiveData 的使用
  • DataBinding 在解决 Drawable 复用问题上的绝妙使用
  • DataBinding 的注意事项 和 屡试不爽的排错技巧
  • 综上

DataBinding 的目标只有三个

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

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

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

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

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

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

注:本文配套的 DataBinding 测试代码,已更新到《重学安卓》项目(不要慌,文末链接给出)

DataBinding 问世前的混沌世界

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

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

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

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

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

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

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

那怎么办呢?

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

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

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

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

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

public class User extends BaseObservable {

    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

在 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。

top Created with Sketch.