Activity 间数据传递
本教程共 100 篇 · 第 28 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
28. Activity 间数据传递
本节目标:学会用 Intent extras 和 Bundle 在 Activity 间传数据,掌握旧的 startActivityForResult 和新的 Activity Result API,知道怎么回传结果。
传数据的基本方式
Activity 间传数据主要靠 Intent:
- 正向传:启动方把数据放进 Intent,被启动方取出来。
- 反向传:被启动方把结果回传给启动方(如选择联系人后返回)。
Intent extras 传数据
启动方放数据
val intent = Intent(this, DetailActivity::class.java).apply {
putExtra("user_id", 1001L) // Long
putExtra("user_name", "张三") // String
putExtra("is_vip", true) // Boolean
putExtra("score", 95.5) // Double
}
startActivity(intent)
putExtra 有基本类型的重载,key 是字符串。
被启动方取数据
class DetailActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userId = intent.getLongExtra("user_id", 0L) // 第二个参数是默认值
val userName = intent.getStringExtra("user_name") ?: "未知"
val isVip = intent.getBooleanExtra("is_vip", false)
val score = intent.getDoubleExtra("score", 0.0)
setContent {
Text("用户:$userName,VIP:$isVip")
}
}
}
Tip
- 取字符串等可空类型时,用 Elvis 给默认值:
getStringExtra("name") ?: "未知"。避免 null 导致问题。 - key 写错或类型取错,
getXxxExtra返回默认值,不会崩。但逻辑会错,建议把 key 定义成常量复用。
用常量管理 key
object DetailActivity {
const val EXTRA_USER_ID = "user_id"
const val EXTRA_USER_NAME = "user_name"
}
// 启动
Intent(this, DetailActivity::class.java).apply {
putExtra(DetailActivity.EXTRA_USER_ID, 1001L)
}
// 取
val id = intent.getLongExtra(DetailActivity.EXTRA_USER_ID, 0L)
避免 key 拼错,IDE 能补全。
Bundle:打包一组数据
Bundle 是键值对容器,能一次传一组数据:
val bundle = Bundle().apply {
putLong("user_id", 1001L)
putString("user_name", "张三")
putBoolean("is_vip", true)
}
val intent = Intent(this, DetailActivity::class.java).apply {
putExtras(bundle)
// 或直接 putExtra("key", value) 也行
}
取:
val bundle = intent.extras
val userId = bundle?.getLong("user_id", 0L) ?: 0L
Bundle 跟 putExtra 本质一样(Intent 内部就是用 Bundle 存),看习惯。数据多时 Bundle 更整洁。
传自定义对象
Intent extras 只能存基本类型和 Parcelable/Serializable。传自定义对象有两种方式:
方式一:实现 Parcelable
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class User(
val id: Long,
val name: String,
val email: String
) : Parcelable
// 传
intent.putExtra("user", user)
// 取
val user = intent.getParcelableExtra<User>("user")
@Parcelize 是 Kotlin 插件,自动生成 Parcelable 代码。配置:
plugins {
id("kotlin-parcelize")
}
Note
- 传对象优先用 Parcelable,比 Serializable 快很多。
@Parcelize让实现零成本。
方式二:传 ID 而不是对象
更推荐的做法:只传 ID,被启动方自己去查数据源。
// 启动
intent.putExtra("user_id", userId)
// 被启动方
val userId = intent.getLongExtra("user_id", 0L)
viewModel.loadUser(userId) // 从数据库/网络查
好处:避免对象过大导致 TransactionTooLargeException,数据总是最新的。这是官方推荐做法。
回传结果:Activity Result API
经典场景:A 启动 B,B 处理完把结果回传给 A(如选择联系人、裁剪图片、登录回调)。
旧方法:startActivityForResult(已废弃)
// 已废弃,别用
startActivityForResult(intent, REQUEST_CODE)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
val result = data?.getStringExtra("result")
}
}
问题:requestCode 散落,耦合严重,难维护,类型不安全。已废弃。
新方法:Activity Result API
AndroidX Activity 1.2+ 提供的现代化方案,用 registerForActivityResult 注册 launcher:
class MainActivity : ComponentActivity() {
// 注册一个 launcher,定义输入输出类型
private val pickContact = registerForActivityResult(
ActivityResultContracts.PickContact()
) { contactUri: Uri? ->
// 回调,contactUri 是返回结果
if (contactUri != null) {
// 处理选中的联系人
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
button.setOnClickListener {
pickContact.launch() // 启动
}
}
}
要点:
registerForActivityResult必须在onCreate之前调用(作为字段初始化或init块),不能在运行时动态注册。- 用
launch()启动,结果回到 Lambda。 - 类型安全:编译器知道输入输出类型。
自定义契约传数据和接收结果
// 自定义契约:输入 String,输出 String
class InputOutputContract : ActivityResultContract<String, String>() {
override fun createIntent(context: Context, input: String): Intent {
return Intent(context, ProcessActivity::class.java).putExtra("input", input)
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
if (resultCode != Activity.RESULT_OK) return null
return intent?.getStringExtra("output")
}
}
// 使用
private val processLauncher = registerForActivityResult(InputOutputContract()) { output ->
output?.let { showResult(it) }
}
// 启动
processLauncher.launch("原始数据")
// 被启动方回传
class ProcessActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val input = intent.getStringExtra("input")
// 处理...
val result = "处理后的结果"
val resultIntent = Intent().putExtra("output", result)
setResult(RESULT_OK, resultIntent)
finish()
}
}
内置契约
AndroidX 提供很多现成的契约,省得自己写:
| 契约 | 作用 |
|---|---|
StartActivityForResult | 通用,启动任意 Activity |
PickContact | 选联系人 |
TakePicture | 拍照 |
TakePicturePreview | 拍照预览 |
GetContent | 选文件("image/*" 等) |
CreateDocument | 创建文档 |
OpenDocument | 打开文档 |
RequestPermission | 请求单个权限 |
RequestMultiplePermissions | 请求多个权限 |
// 请求权限
private val cameraPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted: Boolean ->
if (granted) {
openCamera()
} else {
showDenied()
}
}
cameraPermission.launch(Manifest.permission.CAMERA)
Tip
- 权限请求也用 Activity Result API,比老的
onRequestPermissionsResult干净。第 83 章细讲权限。
Compose 里的数据传递
Compose 单 Activity 架构里,页面间传数据用 Navigation 的参数,不走 Intent:
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(onUserClick = { userId ->
navController.navigate("detail/$userId")
})
}
composable(
route = "detail/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.LongType })
) { backStackEntry ->
val userId = backStackEntry.arguments?.getLong("userId") ?: 0L
DetailScreen(userId)
}
}
回传结果用 savedStateHandle 或共享 ViewModel,第 41 章 Fragment 通信会涉及类似思路。
数据大小限制
Intent extras 通过 Binder 传输,有大小限制(约 1MB)。传大图、大列表会崩 TransactionTooLargeException。
解决方案:
- 传 URI 而不是数据本身。
- 传 ID,对方自己查。
- 用文件/数据库共享。
- 大图用
FileProvider临时授权。
小结
正向传数据用 Intent extras 或 Bundle,取用 getXxxExtra。传对象用 @Parcelize Parcelable,但推荐传 ID 让对方查。回传结果用 Activity Result API(registerForActivityResult + launch),别用废弃的 startActivityForResult。权限、拍照、选文件等有内置契约。Compose 用 Navigation 参数传数据。第 4 部分结束,下一部分学 Intent。