4、Intent与页面跳转:显式Intent与隐式Intent的使用,Activity之间的数据传递

各位同学,今天我们来聊聊Android开发里最基础、也最核心的一个话题——页面跳转。说白了,就是怎么从一个界面跑到另一个界面去。

我记得刚入行那会儿,有个同事把整个App做成了一个单Activity结构,所有逻辑全塞在一个文件里。那代码,啧啧,简直不忍直视。后来我接手重构,第一件事就是把页面拆开,用Intent把它们串起来。嗯,这才是Android该有的样子。

4.1 什么是Intent?

Intent,翻译过来是“意图”。你想想看,你想从A页面到B页面,总得有个“意图”告诉系统吧?Intent就是干这个的。

它有两种玩法:显式Intent和隐式Intent。我个人的习惯是:能显式就显式,除非你真的需要系统帮你“匹配”目标。

4.2 显式Intent

显式Intent,就是直接指定要跳转到哪个Activity。就像你打车,直接告诉司机“去北京南站”,清清楚楚。

// 最简单的显式跳转
val intent = Intent(this, TargetActivity::class.java)
startActivity(intent)

我在项目中遇到过一个问题:有同事在Fragment里直接用了this作为Context,结果崩溃了。记住,Fragment里要用requireContext()或者activity

小技巧:如果你在非Activity类里(比如ViewModel)需要启动Activity,记得传ApplicationContext,但要注意加上FLAG_ACTIVITY_NEW_TASK标志。

4.3 隐式Intent

隐式Intent就有点意思了。你不指定具体目标,而是描述“你想干什么”,让系统帮你找合适的Activity来处理。就像你喊一声“我要去火车站”,系统会看看哪个司机愿意接单。

// 打开浏览器
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.example.com")
startActivity(intent)

这里有个坑,我曾经踩过:如果手机上没有任何App能处理这个Intent,你的App会直接崩溃。所以一定要做安全检查:

if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
} else {
    // 提示用户没有可用的应用
}
注意:隐式Intent需要在AndroidManifest.xml里配置。别问我怎么知道的——我有个项目上线后才发现某个Activity没配filter,用户死活跳不过去。

4.4 Activity之间的数据传递

跳转页面只是第一步,很多时候你还需要带点数据过去。比如从商品列表页跳到详情页,总得告诉详情页“你要展示哪个商品”吧?

4.4.1 使用putExtra

最直接的方式,就是用putExtra往Intent里塞数据:

// 发送方
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("product_id", 1001)
intent.putExtra("product_name", "Android性能优化实战")
startActivity(intent)

// 接收方
val productId = intent.getIntExtra("product_id", 0)
val productName = intent.getStringExtra("product_name")

嗯,这里要注意:getStringExtra可能返回null,最好给个默认值。我习惯用Kotlin的Elvis运算符:

val name = intent.getStringExtra("product_name") ?: "未知商品"

4.4.2 使用Bundle

当你要传的数据比较多时,一个个putExtra就显得有点啰嗦了。这时候用Bundle会更清爽:

// 发送方
val bundle = Bundle().apply {
    putInt("product_id", 1001)
    putString("product_name", "Android性能优化实战")
    putDouble("price", 59.9)
}
val intent = Intent(this, DetailActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)

// 接收方
val bundle = intent.extras
if (bundle != null) {
    val id = bundle.getInt("product_id")
    val name = bundle.getString("product_name")
    val price = bundle.getDouble("price")
}
核心要点:Bundle本质上是一个键值对容器。它支持基本类型、String、Serializable、Parcelable等。我个人强烈建议:传递自定义对象时,用Parcelable而不是Serializable。为什么?Serializable用了反射,性能差很多。我在一个列表页传了100个对象,用Serializable卡了快2秒,换成Parcelable后瞬间完成。

4.5 startActivityForResult与ActivityResultLauncher

有时候你跳转到另一个页面,不只是为了展示,还想等它返回一个结果。比如你打开相机拍照,拍完后要把照片拿回来。

4.5.1 老方式:startActivityForResult

这是Android早期的方式,现在已经被标记为废弃了。但很多老项目里还在用,我简单提一下:

// 发送方
val intent = Intent(this, EditActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_EDIT)

// 接收结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK) {
        val result = data?.getStringExtra("result_data")
        // 处理结果
    }
}

说实话,这个方式问题很多。比如Fragment和Activity的onActivityResult容易搞混,而且代码分散,不好维护。我曾经在一个项目里排查了半天,才发现是Fragment里调了startActivityForResult,但结果被Activity拦截了。

4.5.2 新方式:ActivityResultLauncher

Android官方后来推出了Activity Result API,用起来舒服多了:

// 第一步:注册launcher
private val editLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        val data = result.data
        val resultText = data?.getStringExtra("result_data") ?: ""
        // 处理结果
    }
}

// 第二步:启动
val intent = Intent(this, EditActivity::class.java)
editLauncher.launch(intent)

你看,代码集中了,而且类型安全。再也不用担心requestCode冲突了。

我的建议:新项目一律用ActivityResultLauncher。如果你还在维护老项目,也尽量逐步迁移。别问我为什么——有一次我因为requestCode用了同一个值,两个回调互相覆盖,查了整整一个下午。

4.6 知识体系总览

说了这么多,我们来画个图,把今天的内容串起来:

Intent 知识体系 Intent 显式 Intent 隐式 Intent 指定目标 Activity 类名 数据传递:putExtra / Bundle Action + Data 匹配 需配置 intent-filter 结果返回:Launcher / onActivityResult 显式 隐式 结果返回

4.7 避坑指南

最后,我把自己这些年踩过的坑总结一下,你们遇到了直接对照着查:

问题 原因 解决方案
跳转后崩溃 目标Activity未在Manifest注册 检查AndroidManifest.xml
隐式Intent无响应 没有App能处理该Intent 用resolveActivity检查
数据传不过去 key拼写错误或类型不匹配 定义常量统一管理key
onActivityResult不触发 Fragment和Activity混淆 改用ActivityResultLauncher
传递大对象卡顿 用了Serializable 改用Parcelable

好了,这一章的内容就到这里。Intent这东西,说白了就是Android的“交通系统”。你掌握了它,页面之间的流转就畅通无阻了。下次遇到页面跳转的问题,记得回来翻翻这张图。


公众号:蓝海资料掘金营,微信deep3321