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

作品简介
往期回顾专栏目录更新动态优惠政策版权须知
温馨提示:如这是第一次接触《重学安卓》,可通过上述链接快速了解《重学安卓》专栏,获取 专栏目录、试读内容、更新动态 和 发展状况。
截至目前,专栏已对 体系化文章 实施 3310 余次修订,数十位群友告诉我,受专栏启发 亦开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创开源库 和 提供内推机会,订阅后可随时进群交流。

·

Note 2021.8.18 新版本提示:
本文最初发行于 2019.7.19,在此期间,我们从未停下分享和交流的脚步,
为此,一方面,我们不断为 LiveData 篇提供加餐和翻新,另一方面,不断萌生和沉淀新的见解,
时隔多年,本文迎来 “重制版” ——《吃透 LiveData 本质,为 SharedFlow 添上安全翅膀》,通过 “循序渐进、言简意赅” 的叙述方式,重新阐述对 LiveData 的完整理解,
建议以 “重制版” 内容为准,而打满细节补丁的本文,则可作为 “工具书” 来查阅和参考。

·

Note 2021.5.25 重要提示
阅读本文最佳时机是,您已吃过 阅读 “源码” 或 “源码分析文” 时 找不到头绪的苦
您还没吃过苦,那您先不要着急阅读本文。您得吃过苦,才会有体会。
在您吃够这方面苦后,您才有机会发现,本文正是专用于解决 “如何找到正确打开方式” 的困扰。
我们绝不通篇贴源码,而是基于广泛实践和反思,在累积过大量样本 乃至足以排除掉所有干扰信息后,点到为止揭露 LiveData 框架最核心本质,方便您理解其真实的存在意义,乃至笃信使用项目中。
关于 LiveData 在项目中实践,请另行查阅《GitHub : Jetpack-MVVM-Best-Practice》源码以及《架构模式实践》篇 完整解析。

前言


一次偶然的机会,出于练手需要,经友人介绍 接手并独立负责了 29 个页面、34 个 API、涉及 350 余个细节 的中大型电商软件的开发(感谢友人在项目后期,主动提出帮忙调整布局和对接 JS)。

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


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

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

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

被人云亦云者埋汰的 LiveData


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

“RxJava 也能做到”,你一定听过不少这样的抬杠。

事实上,LiveData 和 RxJava 的关系,就像手与挖掘机的关系 —— 你可以直接用手来颠勺,也可以用挖掘机炒菜,但我们炒菜时 需要能专注 “炒菜场景本身” 潜在的问题 —— 手足够灵活和应对这些问题,

而挖掘机就很难了,不仅 难以专注和优化 “炒菜” 本身还要额外先考虑 怎么把 “硕大的” 挖掘机请到家里、怎么避免额外造成破坏 …

所以,这篇文章存在的缘由,就是为了帮助大家推开那些无聊的争论,来 一睹真实的 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 2021.8.16 加餐:
  • 关于 “观察者模式” 和 “发布订阅模式” 的区别
  • Note 2021.01.12 加餐:
  • 匿名内部类有个 “重复订阅” 的坑需要注意
  • Note 2021.04.12 加餐:
  • 对 “Note 2021.01.12 加餐” 进一步说明
  • 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,也能使 “单向依赖” 成为可能、规避潜在的内存泄漏等问题。

上述三点,如暂无体会,那接下来,请跟随我脚步一起来铺垫 LiveData 来龙去脉。

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 个需求也解决了。

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

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

注:对 “一致性” 的概念不熟悉的朋友可参见《架构组件 “一致性” 概念》篇的完整解析。

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


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

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

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


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

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

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

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

Why?

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

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

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


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

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

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

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

Note 2020.11.11:
有不少小伙伴表示,在工作中亲身经历后,隐约想起,原来这就是专栏中提到的 “多页面状态同步不一致” 的棘手问题,因而出于 方便理解 和 加深印象 的考虑,这里就 “唯一可信源” 的本质和案例 分别做了两次加餐,感兴趣的小伙伴可自行查阅:
关于 “唯一可信源” 的本质特征和设计原理,详见下文 Note 2020.10.14 加餐 的解析。
关于 “唯一可信源” 在生活中的案例 详见以下 Note 2020.11.11 加餐 的举例说明。
👆👆👆 划重点


创作时间: