性能与稳定性
性能优化的原则:先建立度量,再谈优化;没有基线的一切「我觉得卡/快了」都是噪声。稳定性同理:没有崩溃采集,就没有「稳定」可言。本页按「启动 / 流畅 / 内存 / 包体积 / 稳定性」五条主线展开,最后给质量门禁。
建立度量体系
先回答四个问题,再动手:
- 启动时间:冷启动(杀进程后点开)到可交互(TTI)多少 ms?按设备档位分位统计(P50/P90);
- 流畅度:帧率、掉帧率(卡顿时长占比)、ANR/卡死率;
- 内存:RSS 峰值、OOM 率、内存警告次数;
- 稳定性:崩溃率(按版本)、崩溃 TOP 榜、崩溃到归因的时长。
工具:各平台官方性能采集 + 三方监控(Sentry / Bugly / Firebase Crashlytics / 自建);关键指标走「发布前后对比」而非单点看。
冷启动优化
冷启动时间线(iOS 为例):进程创建(dyld 加载动态库)→ main → UI 首帧 → 可交互。任何「启动时同步做」的东西都在这条线上。
| 手法 | 说明 |
|---|---|
| 减少启动同步初始化 | Application/AppDelegate 里能后移的后移:SDK、日志、埋点都异步或懒初始化 |
| 避免启动即开大库 | 需要时再加载(DB、图片解码、加密库) |
| 首帧优先 | 首页先用骨架屏/本地缓存渲染,再异步填充 |
| 动态库/模块瘦身 | iOS 主二进制之外的动态库逐个评估能否内联/按需 |
| 启动任务清单化 | 分「首帧前必须」与「首帧后可」两组,后组并行且可丢 |
| 数据预取窗口 | 首屏后空闲期(idle)再预拉下一页/图片 |
启动监控要能回答:「哪个函数占了大头」(instrument 启动跟踪 / 自定义打点),而不是凭感觉删初始化。
流畅度与帧预算
- 16ms 帧预算:一帧内主线程 + 渲染不能超过预算,超了就是掉帧。卡顿是「主线程被占」或「渲染超时」两类;
- 主线程只做 UI:布局、网络解析、图片解码、数据库 IO、加密全部离开主线程;
- 列表是重灾区:
- 懒加载(ListView.builder / FlatList / LazyColumn 已讲,见各平台页);
- item 复用后别做重活;图片解码做小图先行;
- 动画/滚动触发的大面积重建:加缓存/边界(RepaintBoundary、shouldComponentUpdate 等价物、memo);
- 卡顿定位流程:复现 → 抓主线程栈(时间片采样)→ 看是「布局/绘制」还是「逻辑占用」→ 修对应端。
内存:泄漏与 OOM
| 关注点 | 做法 |
|---|---|
| 泄漏 | 强引用循环、静态持有 View/Context、未注销观察者/监听;用 LeakCanary/Instruments 工具定期体检 |
| 大图 | 采样解码、缩略图、图片库自带缓存与回收 |
| 缓存上限 | 内存缓存设容量与低内存清理(iOS memory warning / Android onTrimMemory) |
| 页面退出清理 | 页面销毁时停动画、释放大资源引用、取消在途请求 |
| OOM 归因 | OOM 常无崩溃栈,用「内存水位 + 时间线」回溯到具体页面/操作 |
一个反直觉点:内存与卡顿互相放大——内存吃紧触发系统清理/GC/换页,表现为「莫名卡顿」。内存曲线也是性能指标。
包体积
- 算账:包体积直接吃下载转化率与更新成功率;先把「各类资源占多少」审计出来(资源/代码/三方库/原生二进制);
- 削减路径:图片转 WebP/AVIF、去重复与未用资源(iOS asset catalog 合并、Android resource shrinking)、代码按需(模块化后动态特性/按需下载资源)、三方库体检(常有大库只用一个小函数)、iOS 拆架构包注意投递规则变化按当前平台策略评估;
- 别用「压缩壳」逃避审计:包内实际体积没变,只是拆包策略/安装后解压,优化目标是真实交付内容。
稳定性:崩溃与 ANR
- 采集三件套:崩溃日志(栈 + 现场)、ANR/卡死采样(Android main thread dump)、内存水位快照;
- 符号化是前提:iOS dSYM、Android mapping(R8/混淆后必须保留并随版本上传),不然堆栈全是乱码——上传 mapping 的自动化要和发版同一条流水线;
- 崩溃分级:按「影响面 × 频率」排优先级:高频小崩溃比低频大崩溃更伤口碑;按版本对比崩溃率决定是否回滚(配合release 页灰度机制);
- 三方崩溃服务对比:Sentry(自托管/云)、Bugly(腾讯,国内渠道友好)、Firebase Crashlytics(Google 生态)——按数据合规(国内数据本地化)选型,别只看功能;
- 稳定性 SLO:给崩溃率定「红线」(如新版本上线 24h 崩溃率超 X 自动暂停放量),发布节奏与监控联动。
日志与问题回溯
用户反馈「刚才闪退/卡死」时,靠三样东西回溯:
- 路径日志:关键操作打点(进入页面、发起请求、收到响应、异常分支),崩溃/ANR 现场附最近 N 条;
- 版本与环境上下文:App 版本、系统版本、设备型号、网络类型、账号维度——按版本聚合才能快速圈定;
- 会话回放(可选):重交互产品可加行为序列,注意隐私合规与脱敏。
日志纪律:调试日志不上生产;生产日志分级(error 必传、info 采样);token/手机号等一律脱敏(呼应网络层与存储)。
质量门禁与常态节奏
- 发布门槛(示例):冷启动 P90 不超过目标、新版本崩溃率对比前版不恶化、无新增 TOP 崩溃、内存水位回归通过;
- 常态化:每个版本跑一次「性能回归清单」(启动、列表滚动、图片流、弱网),结果进周报;
- 技术债:把「性能不达标但能发」设为需要显式决策的事项,而不是默认放行。
常见坑速查
| 坑 | 现象 | 对策 |
|---|---|---|
| 无基线就优化 | 改来改去「感觉好了」 | 先建指标,P50/P90 分设备 |
| 启动初始化全家桶 | 冷启动随 SDK 增长越来越慢 | 任务分级:首帧前/后,懒初始化 |
| 主线程做解码/IO | 偶发卡顿难复现 | 时间片采样主线程,抓现行 |
| 泄漏不体检 | 内存曲线逐日走高 | LeakCanary/Instruments 每版本体检 |
| 图片原图直载 | OOM | 采样/缩略/缓存上限 |
| mapping/dSYM 丢失 | 崩溃栈全乱码 | 符号文件与发版同流水线归档 |
| 只看崩溃率均值 | 小流量版本数据失真 | 分版本/分端对比 + 样本量校验 |
| 生产日志裸奔 | token/隐私进日志 | 分级 + 脱敏层 |
| 低内存不响应 | 被系统杀、体验中断 | 内存警告回调里主动释放 |
| 回滚没有开关 | 只能靠发版 | feature flag 先行,发版兜底 |
| 性能回归没人管 | 每版悄悄慢一点 | 发版前性能回归清单成文 |
检查清单
- [ ] 启动 / 流畅 / 内存 / 崩溃指标已采并有基线;
- [ ] 冷启动已按「首帧前/后」分档优化,启动打点能定位大头函数;
- [ ] 列表与图片走懒加载 + 缩略 + 缓存上限;
- [ ] 每版本内存体检与泄漏扫描通过;
- [ ] 崩溃符号化全自动,崩溃率发布对比 + 自动暂停阈值生效;
- [ ] 日志分级、脱敏、现场上下文齐全;
- [ ] 性能回归清单进入发版流程。
相关链接:网络层与本地存储 · 打包、签名与发布 · 桌面应用 · 前端性能(Web 侧对照) · 写作规范见客户端开发索引。