C32476f5682da38173d38682a28edfe1
重学安卓:LiveData 鲜为人知的 身世背景 和 独特使命

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

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

前言

很高兴见到你!

上上上周,通过友人的介绍,我在业余时间 承接并独立负责了 29 个页面、34 个 API、涉及 350 余个细节 的中大型电商软件的开发(感谢友人在项目后期 主动提出帮忙调整布局和对接 JS 😃)。

该软件有成熟的 iOS 实例,有专门适配 Android 的 360dp 设计稿,有 Restful API,所以尽管是第一次为合作方代工,总的来说还是一次满意的合作。

在该项目中,我广泛地采用了 Jetpack 架构组件。

事实上,正因为 依托于 Jetpack 面向标准化的、普适的 架构设计 和 理念规约,我才 得以在如此高强度的、紧凑的研发周期内,完成合作方原以为不太可能完成的任务

在有过这次研发经历后,我越发地想要向所有认识的、或者不认识的人推(qiang)荐(po)使用 Jetpack 架构组件。

并且,在架构组件中,如果说有哪个组件是我首推的,我一定会选择 LiveData。

被人云亦云者埋汰的 LiveData

在此之前,不少人对 LiveData 有着很深的误解,认为 LiveData 是个鸡肋的存在,并且老爱拿 RxJava 和 LiveData 比较。

如果你看到有谁在争论和抬杠:“RxJava 也能做到”,那他一定是在假装些什么。

事实上,RxJava 和 LiveData 的关系,就像物流中心的分拣员和配送员,是分工协作、完全不同的两个工种。

毫不客气地说,喜欢拿 RxJava 和 LiveData 比较的,不仅从未思考过 LiveData 存在的缘由,就连 RxJava,也不过是会一些表面招式,比那些完全没接触过 RxJava 的开发者好不到哪去。

更有甚者 —— 一位在曾在某社区评论区 为争论而争论、所谓多年工作经验的 Android“大前端”,大言不惭地打算将 RxJava 直接用在视图控制器中、一步到位地完成数据请求响应的工作。

这已经不是有没有架构分层思想的问题了,这是连最基本的、关于 Android 视图控制器的基础知识 —— 生命周期、重建机制、状态管理 —— 都没掌握。

所以,今天这篇文章存在的缘由,就是为了帮助大家推开那些无聊的争论 和 嗡嗡作响的噪音,来 一睹真实的 LiveData,它究竟为超高效率、健壮而稳定的软件开发,带来了哪些不可思议的帮助 😉。

文章目录一览

  • 前言
  • 被人云亦云者埋汰的 LiveData
  • LiveData 的目标只有三个
  • LiveData 问世前的混沌世界
  • LiveData 为什么能解决这三个问题?
  • 综上
  • Note 2020.11.11 加餐:
    • 举一个关于 “唯一可信源” 通俗易懂的生活案例
  • Note 2020.2.17 加餐:
    • Fragment owner 最新设计的变更及缘由
  • Note 2020.10.14 加餐:
    • 对 “唯一可信源” 概念本质的补充说明
  • Note 2020.4.11 加餐:
    • LiveData 采用的是 “发布订阅模式”
    • 注意 观察者模式 和 发布订阅模式 的本质区别
  • Note 2020.7.15 加餐:
    • LiveData 数据倒灌 背景缘由全貌 独家解析
  • Note 2020.09.14 加餐:
    • 对 “LiveData 不宜用在 Repository” 的解读
  • Note 2020.11.06 加餐:
    • 对 EventBus 适用场景的解析

LiveData 的目标只有三个

这篇文章我反反复复地写了又推倒、推倒了又重写,因为立意特别困难 —— 我不能抛开 架构设计 和 理念规约 单独来介绍 LiveData —— 这样的 LiveData 是没有灵魂的。

LiveData 是在 架构设计面向标准化 的背景下诞生的,所以无论如何,我都要首先帮助读者了解状况:到底出于什么缘由,要存在 LiveData 这样的设计。

LiveData 的目标只有三个:

1.在 Lifecycle 的帮助下,实现生命周期管理的一致性,以及订阅者回调的生命周期安全。

2.遵循 “唯一可信源” 理念的约束,提供 “读写分离” 的支持,以便规避 “跨域消息同步” 等场景下 高频 存在的数据一致性问题、使团队新手也能 不假思索写出低风险代码。

3.就算不用 DataBinding,也能使 “单向依赖” 成为可能、规避潜在的内存泄漏等问题。

如果光是阅读了以上三点,你还是不太理解的话,那接下来我就分别介绍 99% 网文都不曾介绍的真实状况,来方便你迅速建立起感性的认识。😉

LiveData 问世前的混沌世界

例如这一次我开发的音频播放软件,其中最核心的功能是播放控制,它总共牵涉到 57 个业务细节。

这里我只拎出最具代表性的 3 个细节:

1.当音乐被暂停、播放时,所有涉及播放元素的界面 都要切换到对应的状态。

2.当切换歌曲时,所有涉及到当前歌曲信息的界面,都要正确展示信息。

3.当切换歌曲时,所有涉及专辑列表的界面,都要正确展示每一项的播放状态。

首页 专辑列表 播放页

(截图来自本次开发的电商软件《FairyTales》,Achieved by KunMinX with doubled patience and Love 😉)

第一条很好理解吧?如图所示,无论我是在 首页、列表页、播放页、还是在 通知栏 触发了播放按钮,一旦播放状态被改变,那么所有包含 展示播放状态 的页面,都要及时同步状态。

第二条的意思是,切歌后,在播放页面等 展示信息的地方,要正确展示当前专辑封面、歌曲的标题、作者等信息。

第三条细节,看似简单 —— 像前面 2 条一样,同步一下列表的 currentIndex (当前 item 的 index)就行?

实际上不是的,它背靠的是 “存在多种播放模式” 的背景,也即,当下可能处于 单曲循环、顺序播放、也可能处于 随机播放模式。

随机播放模式,实际使用的播放列表是一张打乱顺序的列表,是和给用户展示的专辑列表不同的列表。

那么如何处理 事件源 和 订阅者 的关系呢?

首先,既然要控制播放,自然是先封装一个播放控制的单例。

然后,为了解决状态同步的问题,可能首先想到的就是 EventBus —— 为每个页面都注册 EventBus,并且安排好监听方法。当任何一个页面发起通知,其他页面都跟着响应,做出对应的 UI 状态的变化。

看似就解决了 1、2 两个需求。

再者,对于第 3 个需求,因为如果要正确处理 在不同播放模式下,专辑列表和播放列表的正确关系,势必需要唯一可信的对象,以便我们分别在 专辑列表 和 播放列表 中拿到正确的 currentIndex,于是我们在当前页面通过单例拿到正确的 index,并通过 EventBus 来通知上一页(例如当前在播放页,上一页是专辑页)的列表做出变化。

看似第 3 个需求也解决了。

本以为这样需求就轻松解决了,没想到,这么做,出错的概率却是大大增加的,为什么呢?

首先,如果使用 EventBus,那么我们须在每个监听状态的页面、在特定的生命周期节点中 手动地注册和解绑 EventBus,这使得 因疏忽导致的编码不一致 而带来不可预期错误 的概率大大提升(例如在页面步入 onDestroy 后,收到消息并调用了可能已为 null 的视图实例 … )

对 “一致性” 的概念不熟悉的朋友可参见 《为你还原一个真实的 Jetpack Lifecycle》 中的解析。

并且,在缺乏理念约束的情况下,随意使用 EventBus,将难以追踪 “真正的事件源” —— 事件源只有 1、2 个还好,如果是像今天这个 29 个页面的复杂项目,在调试时,光是找事件源,恐怕就得让人欲仙欲死。EventBus 追踪事件源的复杂度是 n * (n - 1),简言之就是 n²。

再者,在缺乏理念约束的情况下,通过 EventBus 从页面发送通知,难以总是确保消息的正确性 和 可靠性 —— 也许当前页面的信息是过时的呢?那么所有收到通知的页面,拿到的数据也就过时了,这非常影响用户体验。

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

特性 1:遵循唯一可信源理念,解决消息分发的一致性问题

首先,我们需要再次明确的一点是:LiveData 诞生的背景是,Google 希望确立一种标准化、规范化的开发模式,使得就算是团队新手,也能自然而然地遵循这样一种模式。

为了达到这个目的,Google 可谓下了苦工,LiveData 就是在这样的背景下诞生的设计。

它被设计为仅限于负责 数据在订阅者生命周期内的被分发,因此除了 setValue / postValue,以及 observe,你再也看不到别的方法,是的,就是这么纯粹。

正因为如此纯粹、甚至无法独当一面,乃至于你不得不借助 ViewModel 或单例,来完成单方向的数据分发 —— 只要你这样做了,Google 的愿望也就达到了。

为什么呢?

因为 一旦这样做了,你不知不觉便达成了两个目标

一个是单向依赖:UI -> ViewModel -> Data。(单向依赖的好处,我们在下一篇 ViewModel 的专题中重点介绍)

另一个是 从唯一可信源取材,完成数据的分发

唯一可信源,意味着无论是哪个页面发起的对 “改变状态” 的请求,最终所有页面状态的改变,都是来自可信源统一的决策和分发

如此一来,复杂度从 n² 骤减至 1 —— 我们能够十分方便地确认 “只读数据” 的来源,并且 数据一定总是最新的,而不至于取到的是由 ”某个信息滞后的页面“ 所发送的 “过时状态”

想想看,两周多一点的时间,横跨 29 个页面的协调交互,容不得半点错误 —— 排查不可预期的错误所耗费的时间,往往是惊人的

—— LiveData 正是以自己的纯粹和克制,来客观上帮助开发者 不知不觉树立正确的编码习惯。

Note 2020.11.11:

有不少小伙伴表示 在工作中亲身经历后,隐约想起 原来这就是专栏中提到的 “多页面状态同步不一致” 的棘手问题,因而出于 方便理解 和 加深印象 的考虑,这里就 “唯一可信源” 的本质和案例 分别做了两次加餐,感兴趣的小伙伴可随时查阅:

关于 “唯一可信源” 的本质特征和设计原理,详见下文 Note 2020.10.14 加餐 的解析。

关于 “唯一可信源” 在生活中的案例 详见以下 Note 2020.11.11 加餐 的举例说明。

👆👆👆 划重点

Note 2020.11.11 加餐:

举一个关于 “唯一可信源” 通俗易懂的生活案例

群里有小伙伴表示对 “唯一可信源” 的概念有点懵逼,实际上 “唯一可信源” 的设计在生活中十分普遍,我举个公司群发邮件通知的案例:

top Created with Sketch.