Intent 与数据传递
问题
Intent 的作用是什么?显式 Intent 和隐式 Intent 有什么区别?如何在组件间传递数据?
答案
1. Intent 类型
// 显式 Intent —— 指定目标组件
val explicitIntent = Intent(this, DetailActivity::class.java)
explicitIntent.putExtra("id", 123)
startActivity(explicitIntent)
// 隐式 Intent —— 声明动作,由系统匹配
val implicitIntent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://example.com")
}
startActivity(implicitIntent)
// 隐式 Intent 发送前应检查是否有匹配的 Activity
if (implicitIntent.resolveActivity(packageManager) != null) {
startActivity(implicitIntent)
}
2. 数据传递方式
Bundle / putExtra
// 发送
val intent = Intent(this, DetailActivity::class.java).apply {
putExtra("name", "Alice")
putExtra("age", 25)
putExtra("tags", arrayListOf("android", "kotlin"))
}
startActivity(intent)
// 接收
val name = intent.getStringExtra("name")
val age = intent.getIntExtra("age", 0)
val tags = intent.getStringArrayListExtra("tags")
Parcelable(推荐)
// @Parcelize —— Kotlin 插件自动生成 Parcelable 实现
@Parcelize
data class User(
val name: String,
val age: Int,
val email: String
) : Parcelable
// 传递
intent.putExtra("user", User("Alice", 25, "a@b.com"))
// 接收
val user = intent.getParcelableExtra<User>("user")
// Android 13+
val user = intent.getParcelableExtra("user", User::class.java)
Serializable
data class Config(
val theme: String,
val fontSize: Int
) : Serializable
// 传递
intent.putExtra("config", Config("dark", 16))
| 方式 | 性能 | 使用便利性 | 推荐度 |
|---|---|---|---|
| Parcelable | 快(内存序列化) | @Parcelize 很方便 | ⭐⭐⭐ |
| Serializable | 慢(反射序列化) | 简单 | ⭐⭐ |
| Bundle 基本类型 | 最快 | 类型不安全 | ⭐⭐⭐ |
Bundle 大小限制
Intent 传递的 Bundle 数据经过 Binder,总大小限制约 1MB(包括所有正在进行的事务)。大数据应通过 ViewModel、数据库或文件传递。
3. Activity Result API
取代已废弃的 startActivityForResult:
class MyActivity : AppCompatActivity() {
// 注册结果回调
private val launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val data = result.data?.getStringExtra("result")
// 处理返回结果
}
}
// 启动
fun openDetail() {
launcher.launch(Intent(this, DetailActivity::class.java))
}
}
// 内置 Contract 示例
// 选择图片
private val pickImage = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
uri?.let { imageView.setImageURI(it) }
}
// 请求权限
private val requestPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) { /* 权限已授予 */ }
}
// 调用
pickImage.launch("image/*")
requestPermission.launch(Manifest.permission.CAMERA)
4. PendingIntent
PendingIntent 是对 Intent 的封装,允许其他应用或系统在未来某个时间点代替你执行操作:
// 创建 PendingIntent
val intent = Intent(context, DetailActivity::class.java).apply {
putExtra("from", "notification")
}
val pendingIntent = PendingIntent.getActivity(
context,
0, // requestCode
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// 用于通知
val notification = NotificationCompat.Builder(context, "channel_id")
.setContentTitle("新消息")
.setContentText("你收到一条消息")
.setContentIntent(pendingIntent) // 点击通知打开 Activity
.setAutoCancel(true)
.build()
PendingIntent Flag(Android 12+)
Android 12 要求必须显式指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE:
FLAG_IMMUTABLE:推荐,PendingIntent 创建后不可修改FLAG_MUTABLE:仅在需要系统修改 Intent 内容时使用(如直接回复通知)
5. Deep Link
<!-- AndroidManifest.xml -->
<activity android:name=".DetailActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/detail" />
</intent-filter>
</activity>
// 接收 Deep Link
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent?.data?.let { uri ->
val id = uri.getQueryParameter("id")
loadDetail(id)
}
}
常见面试问题
Q1: Parcelable 和 Serializable 的区别?
答案:
| 特性 | Parcelable | Serializable |
|---|---|---|
| 性能 | 快(直接内存读写) | 慢(使用反射 + IO) |
| 实现方式 | 需要手动实现(或 @Parcelize) | 只需实现接口 |
| 适用场景 | Android 组件间传递 | 跨平台、持久化 |
| 存储介质 | 内存 | 内存 / 磁盘 |
Kotlin 使用 @Parcelize 注解后,Parcelable 的实现成本和 Serializable 一样低,因此推荐始终使用 Parcelable。
Q2: 隐式 Intent 的匹配规则是什么?
答案:
隐式 Intent 需要同时匹配 Intent Filter 的三个条件:
- action:Intent 的 action 必须匹配 Filter 中的某一个 action
- category:Intent 的所有 category 必须在 Filter 中都有(
DEFAULT是startActivity自动添加的) - data:URI(scheme、host、path)和 MIME type 的匹配
三个条件都满足才能匹配成功。
Q3: startActivityForResult 为什么被废弃?
答案:
问题:
onActivityResult在 Activity 中是一个大的 switch-case,难以维护- requestCode 管理混乱
- 代码分散在两处(发起处和回调处)
Activity Result API 的优势:
- 回调和发起代码在一起(
registerForActivityResult+launch) - 类型安全的 Contract
- 可在 Fragment 中使用,不依赖 Activity
Q4: PendingIntent 的 Flag 有什么区别?
答案:
| Flag | 行为 |
|---|---|
FLAG_UPDATE_CURRENT | 更新已有 PI 的 extras |
FLAG_CANCEL_CURRENT | 取消已有 PI,创建新的 |
FLAG_NO_CREATE | 不创建,仅返回已有的 PI |
FLAG_ONE_SHOT | 只能使用一次 |
FLAG_IMMUTABLE | PI 创建后不可修改(Android 12+ 推荐) |
FLAG_MUTABLE | 允许系统修改 PI 的 Intent |
Q5: 如何安全地传递大数据?
答案:
Intent/Bundle 有约 1MB 的限制。大数据传递方案:
- ViewModel — 同一 Activity 内的 Fragment 共享
- 数据库/文件 — 传递 ID,接收方从存储中读取
- ContentProvider — 跨进程大数据共享
- Singleton/全局缓存 — 内存中缓存(注意生命周期)
// 传递 ID 而非完整数据
intent.putExtra("user_id", userId)
// 在目标 Activity 中通过 ID 获取
val user = repository.getUserById(userId)