[译] Data Binding 库使用的经验教训

由 [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 平台的用户 [rawpixel](https://unsplash.com/photos/uQkwbaP0UrI?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 拍摄

Data Binding 库(下文中以『DB 库』词语来指代)提供了一个灵活强大的方式来绑定数据到 UI 界面。可是要用一句陈词滥调:『能力越大,责任越大』,仅仅是使用数据绑定,并不意味着你能够避免成为一个优秀 UI 开发者。html

过去的几年我一直在 Android 开发中使用 data binding 库,本文会写出我这一路上了解到的与它有关的一些内容细节。前端

尽量使用 bindings

自定义 binding adapter 是一种给 View 控件轻松提供自定义功能的好方法。和许多开发者同样,我对 binding adapter 研究得稍微深刻,最终总结出一套包含 15 种不一样用途的适配器的类集。java

最糟糕的实践是这类适配器,它们生成格式化的字符串并设置到 TextViews 控件,这些适配器一般仅在同一个布局文件中使用:android

虽然这可能看起来很聪明,可是有三大缺点:ios

  1. 优化它们的过程太痛苦。除非你把代码组织得很是好,不然你可能会有一个包含全部适配器方法的大文件,这与代码内聚和解耦原则相违背。git

  2. 你须要使用 instrumentation 工具来作测试。根据定义,你的 binding adapter 不会有返回值,它们接收一个输入参数后设置 view 的属性。这就意味着你必须使用 instrumentation 来测试你的自定义逻辑,这样会使得测试变得既缓慢又难以维护。github

  3. 自定义 binding adapter 代码(一般)不是最佳选项。若是你查看内建文本绑定[参考这里],你将会看到已经作了许多检查来避免调用 TextView.setText(),这样就节省了被浪费的布局检测。我以为本身陷入了这样的思惟困境:DB 库将会自动优化个人 view 更新。它确实能够作到,但仅限于你使用被谨慎优化的内建 binding adapter的状况。后端

相反的,把你的方法的逻辑抽象为内聚类(我称之为文本建立者类),而后将它们传递给 binding。这样你就能够调用你的文本建立者类并使用内建 view binding:app

这样咱们能够从内建的绑定操做过程当中提升效率,而且咱们能够很是轻松地对建立格式化字符串的代码进行单元测试。less

让你的自定义 binding 适配器变得高效

若是你确实须要使用自定义适配器,由于你所需的功能不存在,请尽可能使其变得高效。个人意思是使用全部标准的 Android UI 优化:尽量避免触发测量/布局操做。

这能够像检查当前使用的视图以及你设置的内容同样简单。这里有一个咱们为 android:drawable 从新实现了标准 ImageView adapter 的样例:

遗憾的是,视图并不老是可以显示咱们须要检查的状态。这里有一个在 TextView 上设置切换最大行的示例。它经过改变 TextView 的 maxLines 属性以及一个延时布局转换(android.view.ViewGroup)来实现切换。

这样你就能够了解它的做用

以前 binding adapter 比较简单而且老是设置了 maxLines 属性和一个点击监听对象。TextView 在 setMaxLines() 被调用后总会触发一次布局,这就意味着每次 binding adapter 启动,一次布局就会被触发。

让咱们改变这个状况。因为此功能与 TextView 是彻底分开的(咱们只是在单击时使用不一样的值调用 setMaxLines()),咱们须要将引用存储为当前状态。幸运的是,『DB 库』为咱们提供了一个手工方式去在 binding adapter 中接收状态。经过提供参数两次:第一个参数接收当前值,第二个参数接收值。

因此这里咱们只需比较当前的新的 collapsedMaxLines 值。若是值实际发生了改变,咱们才去调用 setMaxLines() 等方法。

编辑按: 感谢 Alexandre Gianquinto 在评论中提到『double parameters』功能。

谨慎对待你提供的变量

我一直在慢慢的从新设计 Tivi,使用相似 MVI 的东西,使用优秀的 MvRx 库来使它变得规范化。这在实践中意味着个人 fragment/view 订阅到 ViewModel对象,而且接收 ViewStates 的实例。这些实例包含全部用于显示 UI 的必要状态。

这是一个展现 Tivi(连接)中类的样例:

你能够看到它仅仅是一个简单的数据类,包含了 UI 须要在一个 TV 秀界面上显示的全部细节 UI 元素。

听起来像是传递咱们的 data binding 实例对象的完美选项,让咱们的 binding 表达式来去更新 UI,对吧?好吧这确实有效,可是有一些须要注意的地方,这是因为『DB 库』的工做机制。

在 data binding 中你经过 <variable> 标签声明了输入,而后在书写 binding 表达式时在 view 属性处引用了这些输入变量。当任何被依赖的变量发生变化,『DB 库』都会运行你的 binding 表达式(接着会更新 view)。这个变化检测就是你能够免费获取的很棒的优化。

因此回到个人场景,个人布局最终看起来是这样的:

因此我最终获取一个包含全部 UI 状态的全局 ViewState 实例,而且你能够想象出这些状态常常会发生变化。UI 状态的任何轻微变化都会产生一个全新的 ViewState,并被传递到咱们的 data binding 实例。

因此问题是什么?因为咱们只有一个输入变量,全部的 binding 表达式将会引用变量,这就意味着『DB 库』将没法自由选择运行哪一个表达式。在实际过程当中,这意味着每次变量变化(无论多小的变化)发生时全部的 binding 表达式都会运行。

这个问题与 MVI 这点无关,特别是它只是组合状态的 artifact,与data binding 结合在一块儿使用。

那么你能怎么作呢?

有种替代方法是在布局中显式声明 ViewState 中的每一个变量,而后显式传递组合状态实例中的值,以下所示:

这显然会使开发人员维护和同步更多的代码,但它确实意味着『DB 库』能够优化去运行哪些表达式。若是你的 UI 状态不常常变化(可能在建立时有一些次)而且变量数量较少时,我会推荐使用此模式。

我我的一直在布局中使用单个变量,传入个人 ViewState 实例,并依赖于咱们的视图绑定合理地运行。这就是为何让视图绑定变得高效很是重要。

另外一个须要注意的是 Tivi 是 RecyclerView 的重度使用者,还有 EpoxyData Binding,意思就是在 DiffUtil 中会额外有一些变化相关的计算发生。因此若是你的 UI 也有大量的 RecyclerView 组成,你能够相似上文描述不费事地获取计算这方面的优化。

小步迭代

但愿这篇文章强调了一些能够优化数据绑定实现方案中的一些小事。了解『DB 库』的内部机制能够帮助你提升数据绑定效率,并提升你的 UI 性能。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索