为什么 Android 非要用 Intent 传值?从系统"生杀大权"看懂三大移动端通信哲学
在移动端开发中,经常会遇到从 iOS 转岗过来的开发者提出疑问:
"为什么 iOS 页面之间可以直接使用 Delegate 代理或者 Closure 闭包回调回传结果,而传统 Android 的 Activity 通信,却要依靠
startActivityForResult、Intent 序列化打包这种看起来很笨重的方案?"
表面看只是 API 使用习惯的区别,本质是三套系统在进程模型、内存控制权、组件无状态设计上的底层理念分歧。
很多人会简单归因为"Activity 会被系统销毁",但这只是现象。真正根源是:Android 整套系统是消息驱动优先,而非对象引用驱动。
本文从内核底层逻辑出发,梳理移动端页面通信的演进历程,同时分析鸿蒙(HarmonyOS)是如何在两套设计思路之间做出取舍与平衡。
一、根源:Android 从设计之初就"不信任"内存
1.1 iOS 的前提:内存是稳定的
在 iOS 体系里,应用页面由 UINavigationController 统一管理。只要栈顶的 ViewController 存活,栈底各个页面就稳定驻留在同一个进程堆内存中。
对象生命周期完全可控,因此直接使用 Delegate 代理、闭包持有对象引用,既高效又安全。这是 iOS 通信模型的基石假设------应用内内存不会被系统单方面回收。
1.2 Android 的前提:组件随时可能死
而 Android 选择了一条截然不同路线:优先支持跨进程,组件默认无状态。
Android 诞生于 2008 年,早期移动设备内存资源非常紧张。为保障前台 App 的使用体验,系统服务 ActivityManagerService 拥有最高权限:一旦内存吃紧,可以随时杀死后台的任意 Activity。
这意味着:应用自身无法保证 Activity 对象一定存活。
1.3 真实场景:持有引用会发生什么
我们来模拟一个经典场景:
- 页面 A 打开了页面 B;
- 用户接到电话,系统内存压力飙升,后台的 页面 A 被系统销毁,页面 B 依旧处于前台正常运行;
- 用户在页面 B 完成操作,执行返回;
- 如果页面 B 直接持有页面 A 的接口对象指针,调用
callback.onResult()时------原 A 对象已经消亡,这个回调指针没有任何可以接收结果的宿主,直接触发空指针异常、内存泄漏甚至程序崩溃。
💡 关键认知 :不是单纯"怕崩溃",而是对象引用的语义已经彻底失效。B 手里的指针指向的是一个已经不存在的对象。
1.4 Android 的选择:消息驱动
Android 的 Activity 组件从设计上就预设了两种可能性:
- 随时可能被系统回收销毁
- 支持跨应用、跨进程被拉起
所以它并不依赖共享内存对象引用做通信,而是采用消息驱动 的设计思想。Android 三大基石------Intent、Binder、Bundle------全部传递序列化消息,不传递内存对象引用。
startActivityForResult 就是为应对组件随时重建而诞生的协议:
- 页面 A 不传递对象引用,仅传入一个
requestCode作为通信标识; - 页面 B 关闭时,把结果数据封装进
Intent + Bundle,交由系统中转; - 就算页面 A 之前已经被系统杀死,系统会重新创建 A,再把序列化完成的数据交付给
onActivityResult。
底层本质 :系统只保存 Token、requestCode 这类标识,不保存任何 Java 对象引用 。
onActivityResult不是普通函数回调,而是系统分发的生命周期事件。
二、现代 Android 的解法:Activity Result API
2.1 旧方案的痛点
onActivityResult 虽然解决了组件重建后的数据恢复问题,但存在明显缺陷:
- 代码逻辑分散在
startActivityForResult和onActivityResult两处; requestCode魔法数字满天飞,容易冲突;- 多个请求时需要大量
if‑else分支,耦合度高、维护困难。
2.2 新方案的外观
针对该痛点,Google 在 Jetpack 推出 Activity Result API ,也就是我们常用的 registerForActivityResult。
📖 具体用法可以参考:registerForActivityResult 使用详解
很多开发者第一眼看到它的 lambda 闭包写法,会误以为 Android 终于切换到对象引用回调模型:
javascript
// 外表看着是闭包回调,底层是契约框架
val launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
// 处理返回结果
}
}
// 执行页面跳转
launcher.launch(Intent(this, DetailActivity::class.java))
2.3 新方案的本质:语法糖,不是模型切换
但它只是语法糖 ,底层依旧沿用系统 requestCode + 系统事件分发机制,没有改变 Android 消息驱动的底层模型。它做了一层具备生命周期感知的契约封装:
- 契约化(Contract) :把跳转入参、结果解析逻辑抽离成独立契约,编译期提供类型安全校验;
- 生命周期感知(Lifecycle‑Aware) :API 要求必须在组件
CREATED阶段完成注册。即便 Activity 被销毁重建,框架会自动恢复绑定回调; - 底层不变:依旧依赖系统消息分发机制,只是对外提供了类似闭包的简洁编码体验。
💡 社区中肯观点 :这套 API 优化了开发体验,但并没有解决原生 Activity 组件模型本身过重的问题。在应用内场景,ViewModel + Flow 才是更优雅的解法------把状态从随时会死亡的 UI 组件中剥离出来。
三、鸿蒙(HarmonyOS)的设计思路
作为后发的移动端操作系统,鸿蒙吸收了 Android 和 iOS 两者的优缺点,采用分层架构思想:
应用内部页面通信交给应用自身,跨应用/跨服务通信交给系统接管。
3.1 UIAbility 层:沿用系统无状态通信协议
鸿蒙中 UIAbility 对标 Android 的 Activity。在跨 Ability 跳转、调起系统相机、选择联系人这类场景,同样要面对跨进程、组件随时销毁重建的问题。
因此系统层面依旧采用消息通信机制:
- 通过
context.startAbilityForResult(want)发起跳转请求; - 目标 UIAbility 处理完成,调用
context.terminateSelfWithResult(abilityResult)关闭自身并回传结果; - 底层依靠 Want(对标 Android Intent)完成数据打包传递,保证跨组件通信安全、无状态。
3.2 应用内页面层:现代化导航栈方案
在同一个应用内部,鸿蒙摒弃了 Android 早期多 Activity 的笨重模式,对齐 iOS 和现代 Android 单 Activity 的开发范式:
- 提供 Navigation 导航组件与 Router,页面
NavDestination全部托管在当前 UIAbility 的导航栈内部; - 页面生命周期由应用导航栈管控,栈内页面不会被系统随意单独销毁;
- 支持页面跳转直接传递 PopInfo 闭包回调,搭配
@State/@Observed/@Provide响应式状态能力,实现同进程高效数据同步。
💡 一句话理解鸿蒙的取舍 :跨组件交给系统消息协议,同 Ability 内页面交给应用内存回调,把两套模型的优势都拿到了。
四、移动端架构横向对比
| 维度 | iOS (UIKit) | 传统 Android | 现代 Android | HarmonyOS (Next) |
|---|---|---|---|---|
| 应用内页面通信 | Delegate / 闭包回调 | onActivityResult 序列化消息 | ViewModel + Flow / ActivityResult | Navigation 导航栈 + 状态驱动 / 闭包 |
| 跨组件/跨应用通信 | XPC / NSExtension | startActivityForResult (Binder) | registerForActivityResult | startAbilityForResult (Want) |
| 控制权归属 | 应用导航栈全权管理 | AMS/ATMS 系统强制管控 | 系统 + 单 Activity 应用管控 | Ability 归系统,内部页面归应用管控 |
| 通信底层模型 | 单进程引用驱动;跨进程消息驱动 | 消息驱动(默认) | 消息驱动 + 上层语法糖 | 分层:跨 Ability 消息驱动;同 Ability 内存回调 |
💡 有意思的细节 :iOS 也不是永远可以用 Delegate。一旦遇到相机、分享这类跨进程组件,iOS 同样放弃 delegate 引用,改用系统 completion 回调。只要跨进程,所有平台都必须走向消息序列化模型。
五、结语
各个平台 API 的差异,根源是系统对内存控制权、安全边界的不同取舍:
- iOS 假设应用内部导航栈与内存环境稳定,单进程优先追求引用回调的高效;一旦跨进程同样切换消息模型;
- Android 默认所有组件都可能被杀、支持跨进程拉起,整套系统优先保障组件重建之后的数据可恢复,采用消息驱动为基础;
registerForActivityResult只是改善开发体验,没有改变底层模型; - 鸿蒙 融合现代开发范式:应用内使用轻量导航栈 + 响应式状态,跨应用场景交给 Want 系统契约。
理解这套底层逻辑,就能读懂现代开发的核心理念:把状态数据和 UI 组件生命周期进行解耦。
不要把业务状态保存在随时可能被系统回收的 Activity / Fragment 实例中。只有做到数据与页面解绑,业务代码才能更好地适配不同系统的架构约束------这也是为什么 ViewModel、StateFlow、@State 这类"状态持有者"在所有现代移动端框架中都成为了标配。
参考资料