一、先看全局一个 APP 由哪些部分组成用户看到的只是手机上的界面背后其实是一整套系统┌─────────────────────────────────────────────────────┐ │ 手机客户端APP │ │ 界面层 → 业务逻辑层 → 数据层网络 / 本地存储 │ └──────────────────────────┬──────────────────────────┘ │ HTTPS / WebSocket ▼ ┌─────────────────────────────────────────────────────┐ │ 服务端 │ │ 接入层网关、负载均衡→ 业务服务 → 数据库 / 缓存 │ └──────────────────────────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ 第三方服务 │ │ 推送、支付、地图、短信、CDN、统计分析、崩溃监控 │ └─────────────────────────────────────────────────────┘“APP 架构”通常讨论两件事1. 客户端内部怎么组织代码 ← 本文重点 2. 客户端和服务端怎么配合二、客户端的分层架构几乎所有现代 APP 都按职责分成三层┌──────────────────────────────┐ │ 表现层UI Layer │ 显示界面、响应点击 │ 页面、控件、ViewModel │ └──────────────┬───────────────┘ │ 调用 ┌──────────────▼───────────────┐ │ 领域层Domain Layer可选 │ 纯业务规则 │ UseCase下单、计算优惠 │ └──────────────┬───────────────┘ │ 调用 ┌──────────────▼───────────────┐ │ 数据层Data Layer │ 数据从哪里来 │ Repository │ │ ├─ 远程网络 API │ │ └─ 本地数据库、缓存、文件 │ └──────────────────────────────┘每层的规则层负责不该做表现层把数据显示出来把用户操作传下去直接发网络请求、写数据库领域层业务规则满 30 减 5、库存校验关心界面长什么样、数据存在哪数据层决定数据从网络拿还是从缓存拿关心数据怎么显示依赖方向只能向下上层知道下层下层不知道上层。这样换一套界面或者换一个数据库其他层都不用改。举例外卖 APP 点“下单”用户点「提交订单」 │ ▼ 表现层OrderViewModel.submit() │ 显示 loading ▼ 领域层PlaceOrderUseCase │ 校验地址、计算优惠后的价格 ▼ 数据层OrderRepository.createOrder() │ 调用服务端接口 POST /orders │ 成功后写入本地数据库方便离线查看 ▼ 结果一路返回 → 表现层跳转到支付页面三、常见架构模式MVC / MVP / MVVM / MVI这些模式解决的是表现层内部怎么分工的问题。MVCModel - View - ControllerView ◀──▶ Controller ──▶ ModeliOS 早期的默认模式问题Controller 又管界面又管逻辑代码越写越多被戏称为“Massive View Controller”MVPModel - View - PresenterView ◀──接口──▶ Presenter ──▶ ModelView 只负责显示逻辑全在 PresenterPresenter 通过接口操作 View方便单元测试问题接口很多样板代码多MVVMModel - View - ViewModel—— 目前主流View ──观察数据绑定──▶ ViewModel ──▶ Model ◀────状态变化自动刷新──ViewModel 不持有 View只暴露“状态”View 观察状态状态一变界面自动刷新AndroidJetpack、iOSSwiftUI、Flutter 都主推这种思路MVIModel - View - IntentView ──用户意图(Intent)──▶ ViewModel ──▶ 新状态(State) ──▶ View └────────────── 单向循环 ──────────────────┘整个页面只有一个状态对象数据单向流动状态可预测便于调试和回放和 React / Redux 的思想一致对比模式复杂度可测试性现在的使用情况MVC低差小项目、老项目MVP中好部分老 Android 项目MVVM中好主流MVI较高很好声明式 UICompose、SwiftUI中越来越多MVVM 示例Kotlin Jetpack Compose// 页面状态dataclassOrderUiState(valloading:Booleanfalse,valerror:String?null,valorderId:String?null)// ViewModel只管状态不碰界面classOrderViewModel(privatevalplaceOrder:PlaceOrderUseCase):ViewModel(){privateval_stateMutableStateFlow(OrderUiState())valstate:StateFlowOrderUiState_statefunsubmit(cart:Cart)viewModelScope.launch{_state.valueOrderUiState(loadingtrue)_state.valuetry{OrderUiState(orderIdplaceOrder(cart))}catch(e:Exception){OrderUiState(error下单失败请重试)}}}// View只根据状态显示ComposablefunOrderScreen(vm:OrderViewModel){valstatebyvm.state.collectAsState()when{state.loading-CircularProgressIndicator()state.error!null-Text(state.error!!)state.orderId!null-Text(下单成功${state.orderId})}}Clean Architecture整洁架构在大型项目中常把上面的分层进一步严格化┌──────────────────────┐ │ 框架层UI、数据库、网络 │ │ ┌────────────────┐ │ │ │ 接口适配层 │ │ │ │ ┌────────────┐ │ │ │ │ │ 业务用例 │ │ │ │ │ │ ┌────────┐ │ │ │ │ │ │ │ 业务实体 │ │ │ │ ← 最核心不依赖任何框架 │ │ │ └────────┘ │ │ │ │ │ └────────────┘ │ │ │ └────────────────┘ │ └──────────────────────┘ 依赖方向只能从外向内核心业务代码不依赖 Android/iOS 系统可以独立测试甚至在多个平台复用。四、技术选型原生、跨平台、混合方案代表技术优点缺点适合原生AndroidKotliniOSSwift性能最好系统能力最全两套代码两支团队大厂核心 APP、性能要求高跨平台Flutter、React Native一套代码两端运行接近原生体验部分系统功能需写原生插件中小团队、快速迭代共享逻辑Kotlin Multiplatform业务逻辑共享界面各自原生生态较新想兼顾性能和复用混合 / H5WebView H5、小程序可随时更新不用发版性能和体验较差活动页、运营页面实际中大型 APP 往往是混合的┌─────────────────────────────────────┐ │ 首页、核心流程 → 原生性能好 │ │ 部分业务模块 → Flutter / RN │ │ 活动页、规则页 → H5随时改 │ └─────────────────────────────────────┘五、大型 APP模块化 / 组件化当团队有几十上百人时所有代码放在一个工程里会出现编译慢、互相改冲突、改一处坏一片。于是拆成模块┌──────────────────────────────────────────────┐ │ 壳工程App │ 只负责组装 └───────┬──────────┬──────────┬──────────┬──────┘ ▼ ▼ ▼ ▼ ┌──────────┐┌──────────┐┌──────────┐┌──────────┐ │ 首页模块 ││ 订单模块 ││ 用户模块 ││ 支付模块 │ 业务模块 └────┬─────┘└────┬─────┘└────┬─────┘└────┬─────┘ 互相不直接依赖 └───────────┴─────┬─────┴───────────┘ ▼ ┌──────────────────────────────────────────────┐ │ 基础组件网络、图片加载、存储、日志、路由、UI 库 │ └──────────────────────────────────────────────┘关键问题业务模块之间怎么通信不能直接引用对方的类否则又耦合在一起。常用方式1. 路由用 URL 跳转页面 Router.open(app://order/detail?id123) 2. 接口下沉把接口定义放在公共层实现放在各模块 公共层interface UserService { fun isLogin(): Boolean } 用户模块实现它 订单模块通过服务发现拿到实现并调用 3. 事件总线发布 / 订阅消息如“登录成功”谨慎使用好处每个团队负责自己的模块可以单独编译、单独调试。六、客户端和服务端怎么配合APP │ ▼ CDN ─────────────── 图片、视频、安装包等静态资源 │ ▼ API 网关 ─────────── 鉴权、限流、路由、协议转换 │ ▼ BFF为前端服务的后端 ── 把多个服务的数据聚合成一个页面需要的格式 │ ▼ 业务微服务用户服务 │ 订单服务 │ 商品服务 │ 支付服务 │ ▼ 缓存Redis 数据库MySQL 等 消息队列几个常见设计设计说明BFF首页需要用户、商品、优惠券三份数据BFF 合并成一个接口减少手机请求次数服务端驱动 UI服务端下发页面配置JSONAPP 按配置渲染不发版也能改首页布局长连接聊天、实时订单状态用 WebSocket 等长连接推送APP 不在前台时通过 APNs苹果/ 厂商通道安卓推送消息七、贯穿所有层的通用能力除了业务代码一个成熟 APP 还需要这些基础设施1. 网络层- 统一封装请求、错误处理、登录过期自动刷新 token - 超时重试注意下单等操作要防止重复提交 - 弱网 / 丢包下的处理和上一节内容相关 - 接口缓存先显示缓存再请求最新数据2. 本地存储数据类型存储方式简单配置是否首次启动SharedPreferences / DataStore / UserDefaults结构化数据聊天记录、订单SQLite、Room、Core Data、Realm敏感信息token、密码Android Keystore / iOS Keychain图片、文件文件系统 磁盘缓存3. 离线优先Offline First界面只读本地数据库 ◀── 数据层负责和服务端同步 │ 有网后台同步最新数据写入数据库界面自动刷新 没网照样能看历史数据操作先存本地联网后再上传4. 安全- 全部使用 HTTPS必要时做证书校验SSL Pinning - 敏感数据加密存储不在日志中打印 - 代码混淆、防篡改、防调试 - 关键逻辑价格计算、权限判断放在服务端客户端不可信5. 性能- 启动速度延迟初始化非必要的 SDK - 列表流畅复用列表项、图片按尺寸加载和前面 DPR 的知识相关 - 内存避免内存泄漏大图及时回收 - 包体积资源压缩、按需下载6. 监控与发布- 崩溃监控Firebase Crashlytics、Bugly 等 - 性能监控卡顿、启动耗时、网络耗时 - 埋点统计用户行为分析 - 灰度发布新版本先给 5% 用户 - 功能开关 / AB 测试服务端控制功能是否开启出问题可随时关闭 - 热更新H5、RN、Flutter 的部分内容可不经应用商店更新需遵守平台规则八、架构的演进不要一开始就过度设计阶段 1个人 / 小项目 MVVM Repository一个工程 够用就好 阶段 2小团队、功能变多 加入领域层UseCase、依赖注入Hilt / Koin / Swinject 统一网络、存储、日志等基础组件 阶段 3大团队、多业务线 模块化 / 组件化、路由 跨平台框架、动态化、完善的监控和灰度体系原则架构是为了解决当前的问题而不是为了“看起来高级”。总结1. 整体客户端 服务端 第三方服务 2. 客户端分三层表现层 → 领域层 → 数据层依赖只能向下 3. 表现层模式MVC → MVP → MVVM主流→ MVI单向数据流 4. 技术选型原生 / 跨平台 / H5大型 APP 常常混合使用 5. 大型项目要模块化模块之间通过路由和接口通信 6. 服务端常见CDN、API 网关、BFF、微服务、推送 7. 通用能力网络、存储、离线、安全、性能、监控、灰度发布 8. 架构随项目规模演进不要过度设计一句话好的 APP 架构就是让每段代码只做一件事、只依赖它该依赖的东西这样项目变大时依然改得动、测得了、跑得快。