为什么 Android 非要用 Intent 传值?

为什么 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 组件从设计上就预设了两种可能性:

  1. 随时可能被系统回收销毁
  2. 支持跨应用、跨进程被拉起

所以它并不依赖共享内存对象引用做通信,而是采用消息驱动 的设计思想。Android 三大基石------IntentBinderBundle------全部传递序列化消息,不传递内存对象引用。

startActivityForResult 就是为应对组件随时重建而诞生的协议:

  • 页面 A 不传递对象引用,仅传入一个 requestCode 作为通信标识;
  • 页面 B 关闭时,把结果数据封装进 Intent + Bundle,交由系统中转;
  • 就算页面 A 之前已经被系统杀死,系统会重新创建 A,再把序列化完成的数据交付给 onActivityResult

底层本质 :系统只保存 Token、requestCode 这类标识,不保存任何 Java 对象引用onActivityResult 不是普通函数回调,而是系统分发的生命周期事件。


二、现代 Android 的解法:Activity Result API

2.1 旧方案的痛点

onActivityResult 虽然解决了组件重建后的数据恢复问题,但存在明显缺陷:

  • 代码逻辑分散在 startActivityForResultonActivityResult 两处;
  • 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 这类"状态持有者"在所有现代移动端框架中都成为了标配。


参考资料

相关推荐
dora2 小时前
iOS开发新手的第一行代码
ios
方白羽3 小时前
Android17 重写 MessageQueue,解决 Handler 隐性卡顿
android·app
蜡台4 小时前
Kotlin 零基础完整版实战教程|从语法入门到Android工程实战
android·开发语言·kotlin
律宏阔5 小时前
Android 车载 USB 开发笔记:USB Host、USB 串口、USB-CAN、HID 与系统 API
android
律宏阔5 小时前
Android 车载串口开发笔记:UART、RS232、RS485、串口配置与数据通信
android
YF02116 小时前
遥控器APP端自动重连方案
android
执明wa6 小时前
Android Studio 打包 APK
android·ide·android studio
waiting9711187 小时前
Ubuntu 26.04 + Android14安装与编译教程
android·linux·ubuntu
hunterandroid7 小时前
Android 测试全景:从单元测试到 UI 自动化的完整实践
android·前端