前言

谈到 Android 架构,相信谁都能说上两句。从 MVC,MVP,MVVM,再到时下兴起 MVI,架构设计层出不穷。如何为项目选择合适架构,也成常备课题。

由于架构并非空穴来风,每一种设计都有其存在依据。唯有高频痛点熟稔于心,才能技术选型事半功倍。所以今天我们一起探寻 “架构演化” 来龙去脉,相信阅读后你会豁然开朗。

文章目录一览

  • 前言
  • 原生架构
    • 原始图形化架构

      • 高频痛点 1:Null 安全一致性问题
    • 原始工程架构 MVC
      • 高频痛点 2:成员变量爆炸
      • 高频痛点 3:状态管理一致性问题
      • 高频痛点 4:消息分发一致性问题
  • 它山之石
    • 矫枉过正 MVP

      • 反客为主 Presenter
      • 简明易用 三方库
    • 拨乱反正 MVVM
      • 曲高和寡 DataBinding
      • 未卜先知 mBinding
  • 力挽狂澜
    • 官方牵头 Jetpack

      • 一举多得 ViewModel
      • 读写分离 LiveData
    • 半路杀出 Kotlin
      • 喜闻乐见 ViewBinding
  • 百花齐放
    • 最佳实践 Jetpack MVVM

      • 屏蔽回推 UnPeekLiveData
      • 严格模式 DataBinding
    • 前后通吃 Kotlin Flow
    • 消除样板 MVI
    • 另起炉灶 Compose
  • 综上

原生架构

原始图形化架构

完整软件服务,通常包含客户端和服务端。

Linux 服务端,开发者通过命令行操作;Android 客户端,面向普通用户,须提供图形化操作。为此,Android 将图形系统设计为,通过客户端 Canvas 绘制图形,并交由 Surface Flinger 渲染。

但正如《过目难忘 Android GUI 关系梳理》所述,复杂图形绘制离不开排版过程,而开发者良莠不齐,如直接暴露 Canvas,易导致开发者误用和产生不可预期错误,

为此 Android 索性基于 “模板方法模式” 设计 View、Drawable 等排版模板,让 UI 开发者可继承标准化模板,配置出诸如 TextView、ImageView、ShapeDrawable 等自定义模板,供业务开发者用。

这样误用 Canvas 问题看似解决,却引入 “高频痛点 1”:View 实例 Null 安全一致性问题。这是 Java 语言项目硬伤,客户端背景下尤明显。

高频痛点 1:Null 安全一致性问题

例如某页面有横竖两布局,竖布局有 TextViewA,横布局无,那么横屏时,findViewbyId 拿到则是 Null 实例,后续 mTextViewA.setText( ) 如未判空处理,即造成 Null 安全问题,

对此不能一味强调 “手动判空”,毕竟一个页面中,控件成员多达十数个,每个控件实例亦遍布数十方法中。疏忽难避免。

那怎办?此时 2008 年,回顾历史,可总结为:“同志们,7 年暗夜已开始,7 年后会有个框架,驾着七彩祥云来救你”。

原始工程架构 MVC

时间来到 2013,以该年问世 Android Studio 为例,

工程结构主要包含 Java 代码和 res 资源。考虑到布局编写预览需求,Android 开发默认基于 XML 声明 Layout,MVC 形态油然而生,

其中 XML 作 View 角色,供 View-Controller 获取实例和控制,

Activity 作 View-Controller 角色,结合 View 和 Model 控制逻辑,

开发者另外封装 DataManager,POJO 等,作 Model 角色,用于数据请求响应,

显而易见,该架构实际仅两层:控制层和数据层,

Activity 越界承担 “领域层” 业务逻辑职责,也因此滋生如下 3 个高频痛点:

高频痛点 2:成员变量爆炸

成员声明,动辄数十行,令人眼花缭乱。接手老项目开发者,最有体会。

高频痛点 3:状态管理一致性问题

View 状态保存和恢复,使用原生 onInstanceStateSave & Restore 机制,开发者容易因 “记得 restore、遗漏 save” 而产生不可预期错误。

高频痛点 4:消息分发一致性问题

由于 Activity 额外承担 “领域层” 职责,乃至消息收发工作也直接在 Activity 内进行,这使消息来源无法保证时效性、一致性,易 “被迫收到” 不可预期推送,滋生千奇百怪问题。

EventBus 等 “缺乏鉴权结构” 框架,皆为该背景下 “消息分发不一致” 帮凶。

“同志们,5 年水深火热已过去,再过 2 年,曙光降临”

好家伙,这是提前拿到剧本。既然如此,这 2 年时间,不如放开手脚,引入它山之石试试(就逝世)。

它山之石

矫枉过正 MVP

这一版对 “现实状况” 判断有偏差。

MVP 规定 Activity 应充当 View,而 Presenter 独吞 “视图逻辑” 和 “业务逻辑”,通过 “契约接口” 与 View、Model 通信,

这使 Activity 职能被严重剥夺,只剩末端通知 View 状态改变,无法全权自治视图逻辑。

反客为主 Presenter

从 Presenter 角度看,似乎遵循 “依赖倒置原则” 和 “最小知道原则”,但从关系界限层面看,Presenter 属 “空降” 角色,一切都其自作主张、暗箱操作,不仅 “未能实质解决” 原 Activity 面临上述 4 大痛点,反因贪婪夺权引入更多烂事。

这也是为何,开发过 MVP 项目,都知有多别扭。

简明易用 三方库

基于其本质 “依赖倒置原则” 和 “最小知道原则”,更建议将其用于 “局部功能设计”,如 “三方库” 设计,使开发者 无需知道内部逻辑,简单配置即可使用

Github:Linkage-RecyclerView

我们维护的 “饿了么二级联动列表” 库,即是基于该模式设计,感兴趣可自行查阅。

拨乱反正 MVVM

经历漫长黑夜,Android 开发引来曙光。

2015 年 Google I/O 大会,DataBinding 框架面世。

该框架可用于解决 “高频痛点1:View 实例 Null 安全一致性问题”,并跟随 MVVM 模式步入开发者视野。

曲高和寡 DataBinding

MVVM 是种约定,双向绑定是 MVVM 特征,但非 DataBinding 本质,所以长久以来,开发者对 DataBinding 存在误解,认为使用 DataBinding 即须双向绑定、且在 XML 中调试。

事实并非如此。

DataBinding 是通过 “可观察数据 ObservableField” 在编译时与 XML 中对应 View 实例绑定,这使上文所述 “竖布局有 TextViewA 而横布局无” 情况下,有 TextViewA 即被绑定,无即无绑定,于是无论何种情况,都不至于 findViewById 拿到 Null 实例从而诱发 Null 安全问题。

也即,DataBinding 仅负责通知末端 View 状态改变,仅用于规避 Null 安全问题,不参与视图逻辑。而反向绑定是 “迁就” 这一结构的派生设计,非核心本质。

碍于篇幅限制,如这么说无体会,可参见《从被误解到 “真香” Jeptack DataBinding》解析,本文不再累述。

未卜先知 mBinding

除了本质难理解,DataBinding 也有硬伤,由于隔着一层 BindingAdapter,难获取 View 体系坐标等 getter 属性,乃至 “属性动画” 等框架难兼容。

有说 MotionLayout 可破此局,于多数场景轻松完成动画。

但它也非省油灯,不同时支持 Drag & Click,难实现我们 示例项目 “展开面板” 场景。

于是,DataBinding 做出 “违背祖宗” 决定 —— 允许开发者在 Java 代码中拿到 mBinding 乃至 View 实例 …… ??? 那 DataBinding 不 bind 个寂寞,Null 安全还管不管?

—— 鉴于 App 页面并非总是 “横竖布局皆有”,于是开发者索性通过 “强制竖屏” 扼杀 View 实例 Null 安全隐患,而调用 mBinding 实例仅用于规避 findViewById 样板代码。

至于为何说 mBinding 使用即 “未卜先知”,因为群众智慧多年后即被应验。

力挽狂澜

官方牵头 Jetpack

时间回到 2017,这年 Google I/O 引入一系列 AAC(Android Architecture Components)

一举多得 ViewModel

其中 Jetpack ViewModel,通过支持 View 实例状态 “托管” 和 “保存恢复”,

一举解决 “高频痛点2:成员变量爆炸” 和 “高频痛点 3:状态管理一致性问题”,

Activity 成员变量表,一下简洁许多。Save & Restore 样板代码亦烟消云散。

读写分离 LiveData

而 Jetpack LiveData,通过 protected + mutable 设计,实现单向数据流,从而

解决 “高频痛点 4:消息分发一致性问题”。

所谓单向数据流,即无论请求从何处发起,观察者收到都是从 “唯一可信源” 内部鉴权后统一推送的 “只读消息” ,如此可避免 “多页面、多观察者” 获取 “过时、不实、不一致” 消息。

注:对此如无体会,可参见《吃透 LiveData 本质,享用可靠消息鉴权机制》解析,本文不作累述。

半路杀出 Kotlin

并且这时期,Kotlin 被扶持为官方语言,背景发生剧变。

Kotlin 直接从语言层面支持 Null 安全,于是 DataBinding 在 Kotlin 项目式微。

喜闻乐见 ViewBinding

千呼万唤,ViewBinding 问世 2019。

如布局中 View 实例隐含 Null 安全隐患,则编译时 ViewBinding 中间代码为其生成 @Nullable 注解,使 Kotlin 开发过程中,Android Studio 自动提醒 “强制使用 Null 安全符”,由此确保 Null 安全一致。

ViewBinding 于 Kotlin 项目可平替 DataBinding,开发者喜闻乐见 mBinding 使用。

百花齐放

最佳实践 Jetpack MVVM

自 2017 年 AAC 问世,部分原生 Jetpack 架构组件至今仍存在设计隐患,

基于 “架构组件本质即解决一致性问题” 理解,我们于 2019 陆续将 “隐患组件” 改造和开源。

屏蔽回推 UnPeekLiveData

如,LiveData 根据官方描述,可分别用于 “末流推数据” 及 “页面间通信” 场景。但粘性设定使 LiveData 更倾向于前者场景,在后者场景中易发生不符预期 “数据倒灌” 问题。

为此我们从头梳理 “消息分发” 背景来龙去脉,得出以下结论:

项目语言 DataBinding 可变 State 状态托管和保存恢复 单向数据流
Java 必用 ObservableField Jetpack ViewModel
Kotlin 可不用 可无 Jetpack ViewModel

也即,DataBinding 项目,页面旋屏重建后,DataBinding 可从 ViewModel 拿取绑定的 ObservableField 重新渲染。粘性设定可有可无。

在非 DataBinding 项目,由于现如今 “单向数据流” 结构,末流逻辑皆是 LiveData Observer 回调中完成,因而须 LiveData 粘性设定,自动推送最后一次数据。

由此可见,粘性设定确有其适用场景。但,毕竟是 “mutable 系” 框架先驱,消息鉴权天赋在此,不善用岂不可惜?

于是我们考虑屏蔽其 “粘性设定”,专用于 “应用内 - 页面间 - 生命周期安全 - 来源可靠 - 只读一致” 消息分发,并开源至 Github:UnPeekLiveData 集思广益。

期间 “腾讯音乐” 小伙伴贡献过 v5 版重构代码,用于月活过亿 “生产环境” 痛点治理。

严格模式 DataBinding

此外我们明确约定 Java 下 DataBinding 使用原则,确保 100% Null 安全。如违背原则,便 Debug 模式下警告,方便开发者留意。

具体可参见 Github:KunMinX-MVVM 使用。

前后通吃 Kotlin Flow

通常 Flow 可用于领域层、数据层,实现复杂数据变换与传递。

2021 官方考虑 Kotlin Flow + Lifecycle.repeatOnLifecycle 取代 LiveData。眼见 “末流推数据” 场景被平替,跟风抹杀 “消息分发场景可行性” 亦不绝于耳,诸如 “LiveData 设计之初就不是为这个用”。对此其实大可不必。

鉴于 Kotlin Flow “生产者消费者” 队列设计,可用于诸如 “618 抢单” 等暴力测试场景。 Java 下 mutable 系唯 LiveData 可用,且常规操作 LiveData 足矣。

消除样板 MVI

显然 Kotlin 开发者还可再进一步,于 “表现层” 和 “领域层” 使用 MVI 设计。

MVI 基于 sealed class 加持,可集中接收本页面 events 并分流具体 event。如此从 “唯一可信源” 角度看,“mutable 先驱” 被缩减为唯一实例,从而 mutable/immutable 样板代码缩减为一,不再百忙出错。

不过,样板代码手写出错,其实易解决,例如 Java + MVVM 开发者完全可通过 “自动化工具” 生成样板代码,最简单办法即是在 Android Studio 中写个 main 函数,循环拼装输出代码。

此外 “单向数据流” 未能杜绝 “末端逻辑” 隐患,易在 “逻辑闭环误被打破” 情况下,引发 “请求响应递归循环”。过去两年读者群有过数起类似事故讨论,MVI 同有概率遭遇此问题。

所以,MVI 使用见仁见智,Java 开发者建议 Jetpack + MVVM + 自动化。

另起炉灶 Compose

回到文章开头 Canvas,为实现 View 实例 Null 安全,先是 DataBinding 框架,但它作为一框架,并不体系自洽,与 “属性动画” 等框架难兼容。

于是出现声明式 UI,通过函数式编程 “纯函数原子性” 解决 Null 安全一致。且体系自洽,动画无兼容问题,学习成本也低于 View 体系。

后续如性能全面跟上、120Hz 无压力,建议直接上手 Compose 开发。

注:关于声明式 UI 函数式编程本质,及纯函数原子性为何能实现 Null 安全一致,详见《一通百通 “声明式 UI” 扫盲干货》,本文不作累述。

综上

高频痛点1:Null 安全一致性问题

客户端,图形化,需 Canvas,

为避免接触 Canvas 导致不可预期错误,原生架构提供 View、Drawable 排版模板。

为解决 Java 下 View 实例 Null 安全一致性问题,引入 DataBinding。

但 DataBinding 仅是一框架,难体系自洽,

于是兵分两路,Kotlin + ViewBinding 或 Kotlin + Compose 取代 DataBinding。

高频痛点2:成员变量爆炸

高频痛点3:状态管理一致性问题

引入 Jetpack ViewModel,实现状态托管和保存恢复。

高频痛点4:消息分发一致性问题

消息分发难追溯、过时、不一致,

mutable + 唯一可信源 “单向数据流” 解决,

但 mutable 滋生大量样板代码,于是局部 MVI,

但 “单向数据流” 难杜绝请求响应 “无限循环” 隐患,所以,天下无完美架构,唯有高频痛点熟稔于心,不断死磕精进,集思广益,迭代特定场景最优解。

Android 架构设计与挑选相关推荐

  1. 王家林最受欢迎的一站式云计算大数据和移动互联网解决方案课程 V1之Android架构设计和实现完整训练:HALFrameworkNative ServiceAndroid ServiceBes

    如何理解Android架构设计的初心并开发出搭载Android系统并且具备深度定制和软硬整合能力特色产品,是本课程解决的问题. 课程以Android的五大核心:HAL.Binder.NativeSer ...

  2. Android 架构设计(四):组件化?

    同系列传送门 Android 架构设计(一):设计模式分析_赵星海的博客-CSDN博客 Android 架构设计(二):分包和文件结构_赵星海的博客-CSDN博客_android 分包结构 Andro ...

  3. android 架构份额,Android 架构设计比较分析

    MVC.MVP和MVVM是常见的Android三种架构设计,随着时间的推移,MVC使用率逐渐下降,MVP和MVVM的使用相对比较广泛一些,还是那句话:没有最好的架构,只有最适合的架构. 一.MVC a ...

  4. Android架构设计之MVC/MVP/MVVM浅析

    目录 写在前面 一.案例演示 二.MVC模式 2.1.MVC简介 2.2.MVC模式的使用 2.3.MVC模式的缺点 三.MVP模式 3.1.MVP简介 3.2.MVP模式的作用 3.3.MVP模式的 ...

  5. Android架构设计04-实体类转Json框架

    本文介绍一个Json解析的框架,此框架支持把Bean或者嵌套实体类或者List< Bean >转成Json字符串.代码下载 文章目录 1 调用示例 2 代码实现 2.1 解析入口 toJs ...

  6. 高老师的架构设计_隽语集(AA_0151)

    前言:还是许多人不理解软硬整合与差异化.其原理很简单:例如,一台电视机增加一颗"手势3D"硬件模块,假设其成本是1元,量产1千万台就增加成本1千万元:如果再增加一个软件模块,假设其 ...

  7. Android开发周报:锤子新品发布、实战解析Android架构

    新闻 <锤子Smartisan OS 2.0发布:超200项改进>:锤子科技于8月25日在上海梅赛德斯-奔驰文化中心举行了新品发布会,在此次发布会上,锤子Smartisan OS 2.0也 ...

  8. Android App的架构设计:从VM、MVC、MVP到MVVM

    随着Android应用开发规模的扩大,客户端业务逻辑也越来越复杂,已然不是简单的数据展示了.如同后端开发遇到瓶颈时采用的组件拆分思想,客户端也需要进行架构设计,拆分视图和数据,解除模块之间的耦合,提高 ...

  9. Android项目架构设计深入浅出

    简介:本文结合个人在架构设计上的思考和理解,介绍如何从0到1设计一个大型Android项目架构. 作者 | 璞珂 来源 | 阿里技术公众号 前言:本文结合个人在架构设计上的思考和理解,介绍如何从0到1 ...

最新文章

  1. PyCharm缺少cv2模块怎么办?怎样在PyCharm中安装自己需要的package?
  2. personal-index 我的个人主页的介绍
  3. Palindrome(插入字符变成回文字符串)
  4. ajax绑定事件页面重复提交,ajax防止用户重复提交点击事件
  5. Erlang并发机制 –进程调度
  6. WPF:在XmlDataProvider上使用主-从绑定(Master-Detail Binding)
  7. 儿童节特刊 | 如何练好手眼协调能力(手眼标定详解,附源码)
  8. html边框流动效果,采用css实现流动的边框
  9. IT30:30岁IT男未来10年规划第1篇(2009-2020)
  10. 超全!SLAM论文与开源代码汇总(激光+视觉+融合)
  11. html中的颜色代码详解及图示
  12. 最新影牛视频双端影视视频APP网站源码+详细视频教程
  13. 次世代贴图材质制作的提示和秘籍
  14. Mybatis关于int类型字段的判断
  15. Ta-lib学习笔记02--K线模式识别
  16. 【Linux回炉 目录配置】
  17. HDMI CEC小结
  18. C4专项工程师能力认证 考纲
  19. QQ音乐做直播:与秀场无关,对数字音乐产业有何影响?
  20. Python windows高效截屏

热门文章

  1. Andriod Studio 线性布局(LinearLayout)
  2. 与同事协作一起维护发布同一个npm包,报错You do not have permission to publish
  3. 灰狼(GWO)算法(附完整Matlab代码,可直接复制)
  4. 金蝶K3开发-工业单据自定义控件
  5. 2021-07-24博物馆展览馆应用蓝牙AOA高精度定位导航导览的真实商用案例介绍
  6. 硬件工程师七夕鹊桥设计锦集
  7. 天问一号入轨火星成功,火星,我们来了!
  8. L0 图片汇总(方便回顾)
  9. 适配80mm打印机网页打印自动高度问题
  10. Frida-Dexdump 脱壳工具下载使用以及相关技术介绍