F2eef95a3be78fa6d0238c5eac298858
重学安卓:如何让同事爱上架构模式、少写 bug 多注释?

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

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

前言

很高兴见到你!

经过对《重学安卓》专栏近 1 年的学习和思维演练,相信小伙伴们多已演化成为 “深度思考的老司机”,面对 Jetpack MVVM 架构组件的高频场景 多能瞬间做到 “以不变应万变”、“一句话讲到点上”。

与此同时,有小伙伴询问,能否以 KunMinX 的视角出一期 “架构总览图”,以便留意到 项目源码中 之前未能留意到的细节和用意。

答案是肯定的 😉

背景

人,不是机器,人注定会犯错。

尤其是在 多人协作的软件工程背景下 快速版本迭代的时候。

有限的注意力应始终放在刀刃上,因而那些机械重复的模板代码,应在后台自己默默安排好一切、免除因各种手工操作的失误 而造成的不可预期的后果

架构模式应运而生。

脚手架项目的由来

严格来说,MVC、MVP、MVVM 是一种框架级的架构(Framework-Level Architecture),是 透过配置或约定 来达到 “快速稳定开发” 的目的

正如基于 MVC 框架的 SpringMVC,透过注解的方式 将工程结构事先给开发者配置好,因而开发者可以只关心每一层的逻辑控制实现,而无需操心多者间如何来回交互。

同理,在《MVP、MVVM 关系精讲》中我们分别解析了 MVP 和 MVVM 各自的本质,以及如何将它们 分别用在特定的场景 去解决特定问题

经过为期 1 年的维护,《Jetpack MVVM 脚手架》工程已趋于成熟,因而今天我们破例 通过贴代码的方式 来给大家介绍:为了快速、稳定、不出预期外错误地开发,脚手架工程中分别组织了哪些结构设计 并开源和维护了哪些定制组件。

考虑到其间提及了多人协作软工背景下存在的 高频隐患,因而就算不上手 Jetpack MVVM,也请务必理解这些隐患,以便在遭遇时 有脑回路可做出准确判断

注:本文介绍的 “脚手架” 项目,现已在 GitHub 开源,感兴趣可随时关注。

GitHub:Jetpack-MVVM-Scaffold

文章目录一览

  • 前言
  • 背景
  • 脚手架项目的由来
  • 架构图总览
  • 福利 1:DataBinding 严格模式
  • 福利 2:UnPeekLiveData 发送一次性事件
  • 福利 3:Smooth-Navigation 使转场顺滑
  • Note 2020.10.28 加餐
    • 1.透过 State-ViewModel 托管和恢复状态
    • 2.透过 Callback-ViewModel “唯一可信源” 来跨页面通信
  • 通过 Request 来复用转发逻辑
  • Note 2020.11.03 加餐
    • 正确区分 “可变状态” 和 “只读数据” 的本质及作用
    • 而这也就顺带解析了 为什么有了 ViewModel 还要有 Request
  • 通过 UseCase 管理可叫停的业务
  • 通过 DataResult 回调数据层结果
  • Note 2020.12.01 加餐
    • 对 DataResult v1.1 新设计的补充说明
  • 综上

架构图总览

《Jetpack MVVM 脚手架》项目不仅仅是我一个人的创作,也是集许许多多优秀开发者参与演化的结果。

以下是脚手架项目的架构图,主要包含 表现层、领域层、数据层 三层:

Activity/Fragment、DataBinding、Navigation,ViewModel 位于表现层;

Request、UseCase 等组件位于领域层,夹在 表现层和数据层 中间,提供可复用的转发;

DataRepository 和 本地、远程数据源 则安排在数据层。

考虑到本文的目标主要是:介绍在现有架构组件的基础上,我们分别在 高频场景 下做了哪些改进 以更好地达成 “快速、稳定、不出预期外错误地开发”,因而对于 DataBinding、ViewModel、LiveData 等 “架构组件的本质” 尚不熟悉的朋友,请先透过《Jetpack MVVM 精讲》获取前置知识的铺垫,本文不作累述。

福利 1:DataBinding 严格模式

正如《Jetpack MVVM 精讲》中提到的,我们在表现层使用 DataBinding 而不是 “直接调用视图实例” 或 ViewBinding,是为了 通过 “可观察数据” 间接通知视图刷新,来规避可能存在的 视图实例为 null 的安全隐患。也即 DataBinding 的本质是 解决视图调用的一致性问题

然而与基于函数式编程思想的 Flutter/Jetpack Compose 不同的是,DataBinding 并非是通过纯函数的方式来隔绝手写代码对 “视图实例” 的接触,而是透过 “自动化代码生成” 的方式来为视图实例做 ”判空处理“,

而这也就带来了一个问题 —— 你可以在代码中透过 Binding 实例来调用视图实例 —— 如此等于舍本逐末、前功尽弃。

因而基于对 “解决视图调用一致性问题” 的独家理解,“DataBinding 严格模式” 应运而生,通过它,可使视图调用一致性问题 被彻底 (100%) 解决、安全性与 Jetpack Compose 持平。

很高兴有不少小伙伴告诉我,他们已将这种开发模式用在实际项目的开发中。

考虑到 “按需” 选用的原则,现已抽取为 “依赖库” 独立维护。

GitHub:Strict-DataBinding

温馨提示:

在使用 “DataBinding 严格模式” 后,对于 “属性动画” 等 “对视图实例强依赖” 的场景,可借助 “Motion 动画” 等新式框架代替(确定 Motion 动画的学习成本不足属性动画的 20%,且效果好、收益高,具体视频教程可见我们在《MotionChallenge》的分享)。

如对 Jetpack Compose 基于函数式编程思想 “解决视图调用一致性问题” 的理论基础感兴趣,可详见《事关软件工程安全 的 数据驱动 UI 框架 扫盲干货》的铺垫,此处不做累述。

福利 2:UnPeekLiveData 发送一次性事件

众所周知,LiveData 被设计为用于接收数据,并 作为唯一可信源、以 “生命周期安全” 的方式去给订阅者页面分发数据,也即 LiveData 无需手动解注册,这规避了 “生命周期安全的一致性问题”

与此同时,“页面返场回调” 是个极高频的场景,出于对 “生命周期安全、作用域的限制、事件源的可追溯” 等背景因素 的收集和分析,我们否决了 EventBus、LiveDataBus、Result API 在页面回调场景的使用,取而代之的是透过 LiveData + SharedViewModel 的组合来作为当下的 最优解,因而出路也就指向了解决 LiveData 自身的粘性等问题(也即 解决在返场场景下 “数据倒灌” 等问题)。

如对 “数据倒灌场景的重现”、“背景因素收集和分析的具体决策依据”、“及为何放弃 EventBus、LiveDataBus、Result API” 感兴趣,可详见《LiveData 数据倒灌 背景缘由全貌 独家解析》的铺垫,此处不做累述。

托福于小伙伴们对它的宠爱有加,在过去 3 个月中陆陆续续收到 7 位小伙伴、20 多次 bug 反馈,在一次次的灵感萌生下 最终演化为现在的样子:

Note 2020.10.15:在最新的 v4.0 版本中,参考了小伙伴 Flywith24 WrapperLiveData 遍历 ViewModelStore 的思路,以此提升 “防止倒灌时机” 的精准度。

目前为止,UnPeekLiveData 实现和保留的特点如下:

1.一条消息能被多个观察者消费(since v1.0)

2.消息被所有观察者消费完毕后才开始阻止倒灌(since v4.0)

3.可以通过 clear 方法手动将消息从内存中移除(since v4.0)

4.让非入侵设计成为可能,遵循开闭原则(since v3.0)

并且我们仍保留了 构造器模式 的设计,后续如有定制功能拓展,我们会逐步在 Builder 中添加配置。

考虑到 “按需” 选用的原则,现已抽取为 “依赖库” 独立维护。

GitHub:UnPeek-LiveData

福利 3:Smooth-Navigation 使转场顺滑

早在去年 8 月份,结合小伙伴的反馈,我们在《Jetpack Navigation》篇文末加餐中独家解析了 “Navigation 被设计为 replace 转场的缘由所在”,并给出了该背景下的最优解。

如果仍坚持使用 Fragment 作为页面,且对 “返场通知时机” 等因素的控制没有要求的话,那么将 replace 改为 add、hide 不失为简便的选择。

然而得益于小伙伴的反馈,我们发现了 GitHub 上现有的 “Navigation Add Hide 修改版” 其实都存在一个致命的弊病 —— 在通过 “popUpToInclusive 越级返回时,未正确计数和出栈 Fragment,导致加载了预期外的 Fragment” 的现象

基于此,我们调试并修复了这方面的漏洞,作为 Smooth-Navigation 依赖库分享给大家。

top Created with Sketch.