是故胜兵先胜而后求战,败兵先战而后求胜。——《孙子兵法·形篇》
【NowInAndroid架构拆解】系列文章
- 【NowInAndroid架构拆解】(1)分层设计与模块化
- 【NowInAndroid架构拆解】(2)数据层的设计和实现之model与database
- 【NowInAndroid架构拆解】(3)数据层的设计和实现之network
- 【NowInAndroid架构拆解】(4)数据层的设计和实现之data
- 【NowInAndroid架构拆解】(5)VM层的设计和实现之ForYouViewModel
- 【NowInAndroid架构拆解】(6)View层的设计和实现之Navigation路由
- 【NowInAndroid架构拆解】(7)UI层解析——MainActivity构建过程
- 【NowInAndroid架构拆解】(8)UI层解析——ForYou页面展示
- 【NowInAndroid架构拆解】(9)重新审视NowInAndroid架构设计
- 【NowInAndroid架构拆解】番外篇1之Jetpack Compose Navigation
- 【NowInAndroid架构拆解】番外篇2之Bottom Navigation底部导航
- 【NowInAndroid架构拆解】番外篇3之给xml布局者最佳的Jetpack Compose介绍文章
通过本文可以知道
- 我为什么选择写架构主题
- NowInAndroid的设计哲学
- NowInAndroid的模块化设计
前言
为什么会有这一主题
架构系列是我近期的研究重点,在从事客户端研发工作十余年后,渐渐认识到业务开发只是表象,一百个项目就可能存在一百种业务页面。但在业务之下,良好的架构却是项目能够健康稳定发展的基石。客户端、前端作为用户最先接触到的软件部分,天然具有变化频繁的属性。但究其本质,要解决的是外在交互与内在代码之间的转化问题。《重构》、《代码整洁之道》、《代码大全》等一系列书籍,都在强调“高内聚低耦合”、“对扩展开放,对修改关闭”、“关注点分离”等等。
优秀的架构设计应该如同艺术品一般,让人赞叹其优美。就算做不到一劳永逸的完美,至少它也应当将美的部分与丑的部分隔离开,让在其上工作的工程师感到身心愉悦。
Android软件架构——窥一斑而知全豹
Android客户端开发技术发展至今,历经刀耕火种的MVC,初具雏形的MVP,另辟蹊径的MVVM,以及锦上添花的MVI,至今仍无法定论其架构设计是否已经到达终点。随着Jetpack Compose、Coroutine等新技术的普及,很难说未来会不会再出现一种新的架构思想。
软件架构的本质
软件架构(Software Architecture)本质上解决的是 复杂系统的结构性矛盾,其存在的意义在于为软件系统提供 可持续演进的骨架,是平衡技术、业务、团队和资源的关键决策框架。通过软件架构的结构化设计,能够有效地抵抗熵增,平衡技术业务和资源之间的不同诉求,以及为未来预留演进空间。
NowInAndroid主要功能拆解
NowInAndroid (github.com/android/now…) 是Google官方最新架构设计Demo,应用了目前主流的模块化组件,且仍处于不断更新中。
整个APP可以看做新闻类应用,由3个Tab组成,分别是信息流、收藏、兴趣标签。首次进入APP时,需要选择感兴趣的主题(标签),在选择完成后同步刷新出该主题下的文章列表。可以对文章进行收藏,收藏后该文章出现在收藏Tab中。最后一个标签Tab与信息流Tab的数据是同步的。点击列表中的文章,会跳转到Chrome打开相应网页。
从主要功能推测其实现思路
- 整体采用单一Activity架构,由3个Fragment组成。
- 遵守单一数据源原则,至少有标签、文章、收藏三个数据源
- 由数据驱动,可能采用了MVVM架构
接下来我们结合官方文档(见文末参考资料),对该APP的架构设计思路进行拆解。
NowInAndroid核心设计思路
分层与单向数据/控制流
- 分为数据层、领域层(我更喜欢称之为功能层)、UI层
- 数据自底向上单向流动,采用监听方式(虚线)
- 控制由UI层向数据层单向流动,采用直接传递方式(实线)
应用启动->界面显示数据的数据控制流
- APP启动,将本地-服务器的数据同步任务加入WorkManager队列
- UI层发射Loading状态
- 离线Repo加载并发射用户数据
- 本地-服务器数据同步任务启动
- 从服务器下载最新的信息流
- 通过Retrofit请求服务器
- 服务器返回数据
- 将服务器数据写入数据库
- 完成数据库写入后,发射最新信息流数据
- 将信息流数据与用户数据融合,通过Repo发送融合后的数据至功能层(领域层)
- 领域层组合数据后,发射给UI
- UI状态变为Success,展现最新数据
数据层
根据“单一可信数据源”原则,采用本地数据库作为单一数据源,每次从网络加载最新数据后,会将数据先保存在DB中,保存成功后方能发射给UI。
通过suspend耗时函数直接写入数据库,通过Flow流向上层发射数据。
功能层(领域层)
功能层由用例(Use Case)组成,通过组合数据层Repo发来的不同数据,构成具体的业务场景数据结构,并发送Flow给上层UI。
功能层不包含任何事件处理的定义,理论上,可以把UI层替换为H5、iOS页面,而无需对功能层进行任何改动。
UI层
包括以下两部分:
- 使用J etpack Compose 构建的 UI 元素
- ViewModels
采用密封类(Sealed Class)封装了页面的状态(Loading、Success),ViewModel把功能层发送来的用例冷流进行组合,生成UI状态流,每一个UI状态流对应UI的相应展示规则,此状态流以热流形式发送。
为什么ViewModel底层是冷流而上层是热流?
- 冷流具有懒加载属性,仅在开始订阅时启动加载数据,适用于网络、数据库,减少不必要的IO请求。
- 热流具有实时性、共享数据源属性,适合展现UI的最新状态。
模块化拆分
模块化的好处
现在主流的大型APP均采用了模块化设计,其好处列举如下:
- 可扩展性: 采用关注点分离原则,不同模块之间采用接口通信,内部实现可以自由替换升级。
- 支持并行工作: 减少代码冲突,适合团队高效并行开发。
- 职责明确: 每个模块有明确的责任人。
- 封装/逻辑独立: 每个模块职责独立,关注点分离,便于阅读理解和测试。
- 加快构建: 利用Gradle并行构建,减少构建时间。
- 动态下发: 类似热更新,需要Google Play支持。
- 可重用性: 提供了跨平台重用的可能。
模块化也有其负面效应,模块太多会导致项目复杂度上升,模块太少则起不到相应的解耦效果。对于简单的项目,单一模块反而更加适用。
NowInAndroid模块拆分
可见NIA大体上分为三层:
- app模块: 由应用脚手架组成,将APP正常运行所需要的一切整合在一起,包含UI框架和应用级控制导航。它依赖全部feature和部分core。
- feature模块: 功能层(领域层)模块,包括UI组件和ViewModels。feature彼此之间隔离,可以依赖底层的core。
- core模块: 通用库模块,可以依赖于其它的core。图中可见只有
common
和model
是不会依赖其它core的,它们位于依赖链的最底层。
需要注意,模块化的拆分不是一成不变的,并没有一个一劳永逸的正确答案。架构设计的目的之一,就是在满足现有需求的基础上,为将来可能发生的演变提前做兼容设计。