本节摘要:启动是从点击图标到首屏可交互的全过程,中间经历原生壳初始化、引擎装载字节码、包执行、首次渲染五个阶段;生命周期则是应用在前后台切换时两端各自发出的一套事件,RN 用统一的 AppState 把它们映射起来。本节先定义「启动完成」再拆阶段,然后给出生命周期的双端对照表与资源释放的代码范式——这两块知识是第 8 章启动优化与热更新、第 4 章内存治理的直接前置。
讨论启动性能最容易踩的坑是口径不一:有人拿白屏结束算,有人拿首帧渲染算,用户却只认「能点了」为准。本节的口径是三段式:启动时长等于原生壳就绪、引擎与包装载、首屏可交互三段之和。拆开讲,第一段是原生壳初始化——操作系统拉起应用进程,iOS 侧执行应用代理的启动方法,Android 侧走应用与应用活动的创建流程,初始化原生运行环境;第二段是引擎与包装载——Hermes 引擎启动并加载预编译的字节码(这正是 0.70 起 Hermes 成为默认后启动提速的主因:字节码免去了运行时解析 JavaScript 源码的开销),随后 JS 包开始执行,模块系统按依赖关系初始化,新架构下 TurboModules 让这一步从「全量注册」变成「按需装载」;第三段是首屏可交互——React 完成首次渲染,描述经渲染管线变成真实原生视图,布局绘制完成,用户手势开始有响应。
三段口径的价值在于定位:白屏时间长而首屏快,病灶在第一段(原生壳与引擎);首屏出现但卡住不能动,病灶在 JS 首帧任务(常见于首屏就发起了过多同步初始化)。把模糊的「启动慢」拆成可归因的段落,优化才有的放矢。
应用进入后台、回到前台、被系统回收——这些状态切换在 iOS 与 Android 上各自有一套原生事件,RN 把它们统一映射成 AppState 的三种取值:active 表示前台可交互,background 表示已进入后台,inactive 是 iOS 特有的过渡态(来电、下拉通知中心的瞬间)。对照关系如下表:
| 应用状态 | iOS 侧对应事件 | Android 侧对应事件 | AppState 取值 |
|---|---|---|---|
| 前台可交互 | 成为活跃应用 | 活动恢复可见 | active |
| 失去焦点但未后台 | 进入非活跃(仅 iOS) | 活动暂停 | active 或 inactive 过渡 |
| 进入后台 | 应用进入后台 | 活动停止 | background |
| 被系统回收 | 进程终止 | 进程被杀 | 无事件,需持久化兜底 |
注意最后一行:进程被杀时不会有任何事件通知你,这是双端一致的残酷规则——重要状态必须「随写随存」,不能指望退出回调(第 4 章持久化一节会展开这个纪律)。另外两端有一个经典行为差异:Android 的返回键可以在 JS 层拦截处理,iOS 没有对应的物理返回概念,手势返回由系统接管——这让「退出确认」类逻辑在两端需要不同的实现策略。
监听前后台切换的标准写法如下,重点看订阅如何随组件卸载正确清理——这是第 6 章内存泄漏主题的第一次预演:
import React from 'react'; import { AppState, Text } from 'react-native'; export function SyncGate() { const [state, setState] = React.useState(AppState.currentState); React.useEffect(() => { // 订阅前后台切换事件 const sub = AppState.addEventListener('change', next => { setState(next); if (next === 'background') { // 进入后台:暂停轮询、刷新令牌、上报埋点等收尾动作 // 注意:Android 后台存活时间有限,收尾要快,别赌来得及 } }); // 组件卸载时移除订阅,否则监听器会一直挂在 AppState 上 return () => sub.remove(); }, []); return <Text>当前状态:{state}</Text>; }
工程上还有一条容易被忽略的纪律:进入后台时的「收尾动作」要按优先级裁剪。iOS 给后台任务的宽限时间以秒计,Android 在新版系统上对后台执行限制更紧,把耗时的收尾(比如全量数据同步)放到前台恢复时做,后台只做轻量的(比如暂停动画、写一个时间戳),是双端都适用的保守策略。
测量启动前还要分清两种启动。冷启动:进程全新创建,从操作系统拉起进程开始走完整三段——这是用户「点开应用」的真实体验,也是优化的主战场。温启动:进程还在(后台未被杀),只是把界面拉回前台,基本只走生命周期的前台切换,速度快一个量级。两者混淆会让测量数据完全失真:测试机上反复点开「退出到桌面再点开」,多数时候是温启动——进程大概率还活着;真正的冷启动要用工具主动结束进程后再启动。发布后线上监控区分两者的标志是「进程创建时间戳」,开发期最简单的办法是每次测量前用命令行强制停止应用。
温启动也不是不用管:从后台回到前台时的恢复速度(恢复渲染、重连数据流)同样在用户的体感账本上,尤其是切出去回个消息再切回来的高频动作。优化重点不同——冷启动的敌人是初始化量,温启动的敌人是前台的恢复工作量(重建了多少视图、刷新了多少数据)。测量口径分开立账,优化才不会张冠李戴。
背景:某资讯类应用反馈安卓端冷启动明显慢于 iOS,产品要求给出数据与方案。操作:先在两端各装性能记录方案——原生侧记录壳就绪时间戳,JS 侧在入口首行与首屏挂载完成处各打一个时间戳,三段口径的起止点由此对齐;然后采集五十次冷启动样本(冷启动指进程全新创建,注意排除温启动干扰)。结果:数据显示 Android 端的「引擎与包装载」段明显更长,其中包体积与启动时的同步模块初始化是大头;iOS 端三段分布均匀。解读:Android 慢不是玄学,包装载段的耗时与 JS 包体积正相关,而该项目历史上往包里塞了大量低频使用的初始化代码。变式与后续:短期把低频初始化改为延迟触发(首帧后再做),中期按第 8 章的构建方案拆分与瘦身包体——注意这条结论在新架构下依然成立,TurboModules 治的是「原生模块」的按需化,JS 侧的初始化纪律仍要自己遵守。这个案例也再次演示了本节的核心方法论:拆段、打点、归因,让每一段耗时都有人认领。
至此引擎盖下的世界已经巡完。下一章回到方向盘前:组件、样式与交互,那些你每天真正在写的代码。