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

往期回顾专栏目录更新动态优惠政策版权须知

温馨提示:如果这是第一次接触《重学安卓》,可通过上述链接来访问和快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。

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

·

Note 2021.8.18 新版本提示:

本文最初发行于 2019.7.19,在此期间,我们从未停下分享和交流的脚步,

为此,一方面,我们不断为 LiveData 篇提供了 加餐和翻新,另一方面,不断萌生和沉淀了新的见解,

时隔本文发行两周年零一个月,我们发行了本文的 “重制版” ——《吃透 LiveData 本质,享用可靠的消息鉴权机制》,通过 “循序渐进、言简意赅” 的叙述方式,重新为您阐述对 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,也能使 “单向依赖” 成为可能、规避潜在的内存泄漏等问题。

如果光是阅读了以上三点,你还是不太理解的话,那接下来我就分别介绍 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 的视图实例 … )

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

并且,在缺乏理念约束的情况下,随意使用 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 加餐:

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

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

HR 群发邮件通知 “这周三 集体踏春,为期一天”。收到消息后大家议论纷纷,有抱怨 “为什么不是周五出游、这样刚好就和周末连在一起”,也有抱怨 “为什么不是 2 天,1 天多没劲”。总之大家你一句、我一句,消息一下传开了,但如果没有看过原始邮件的话,你都不知道 哪个说法才是最初且可靠的

然而昨天你和 HR 小丽吃饭的时候,她预先透露了,“公司 这周五 会踏春”,怎么今天就改为周三呢?如果是笔误,那么此时你有两个选择:

一个是自己站在人群中央,大吼 “听我的,都听我的,这个肯定是笔误,昨天我得到的内部消息是周五”,

另一个是私下与小丽沟通,如果是笔误,还请 HR 统一再群发一次邮件 来澄清此事。

哪种做法可信度最高呢?当然是后者,因为前者的话,没有参与过公司高层的决策本身,对事情的背景一无所知,可信度一般;后者是公司代表,如果你想统一改变大家收到的通知,最有效的办法就是和这个公司代表沟通,由他来群发邮件。

于是关于这类事情,我们提炼了三个关键:

1.每个人都长着耳朵和嘴巴,好比每个人天生就具备发送和接收 EventBus 消息的能力,只不过明智的人懂得不 “道听途说”。

top Created with Sketch.