首页 / Android 入门教程 / Activity 间数据传递

Android 入门教程

Activity 间数据传递

本教程共 100 篇 · 第 28 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程Activity数据传递Intent extrasActivityResult APIBundle

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。