Intent 机制深度解析
Intent 是 Android 组件之间通信的「消息载体」。理解 Intent,就理解了 Android 组件解耦的核心机制。
Intent 的本质
Intent(意图)是一种消息对象,可以携带:
- 动作(Action):想做什么,如
Intent.ACTION_VIEW - 数据(Data):作用于什么,如
Uri.parse("tel:12345") - 类别(Category):附加分类,如
Intent.CATEGORY_LAUNCHER - Extra:键值对附加数据
- 组件名(ComponentName):明确指定目标组件(显式 Intent 必须)
- Flags:控制行为,如
FLAG_ACTIVITY_NEW_TASK
显式 Intent vs 隐式 Intent
显式 Intent
明确指定目标组件,直接命中,系统无需匹配:
// 应用内跳转:推荐
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("id", 42)
startActivity(intent)
// 跨应用(已知包名和类名):安全性差,通常不推荐
val intent = Intent().apply {
component = ComponentName("com.other.app", "com.other.app.ShareActivity")
}
startActivity(intent)
隐式 Intent
不指定目标,由系统根据 IntentFilter 匹配。适合跨应用调用:
// 打开网页(让用户选择浏览器)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(intent)
// 拨打电话
val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:12345678"))
startActivity(intent)
// 分享文本
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "分享的内容")
}
startActivity(Intent.createChooser(intent, "选择分享方式"))
IntentFilter 匹配规则
在 AndroidManifest.xml 中声明 <intent-filter> 来表明组件能响应何种隐式 Intent:
<activity android:name=".ViewImageActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
匹配规则:隐式 Intent 必须同时通过 Action、Category、Data 三项测试,才能匹配到这个组件:
| 要素 | 匹配规则 |
|---|---|
| Action | Intent 的 Action 必须与 filter 中某个 Action 相同 |
| Category | Intent 的每个 Category 都必须在 filter 中存在;startActivity 隐式启动会自动添加 CATEGORY_DEFAULT |
| Data | URI scheme/host/path 和 MIME type 都要匹配(未指定则忽略该维度) |
检测是否有应用能响应(Android 11+ 需先声明 <queries>):
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
Toast.makeText(this, "没有可以处理此操作的应用", Toast.LENGTH_SHORT).show()
}
PendingIntent:延迟意图
PendingIntent 是对 Intent 的包装,允许其他应用(如系统、通知栏、小部件)在未来代替你的应用执行这个 Intent。
使用场景
- 推送通知的点击动作
AlarmManager定时任务WorkManager完成回调- 桌面小部件的交互
创建方式
// 1. 创建基础 Intent
val intent = Intent(context, DetailActivity::class.java).apply {
putExtra("notification_id", 42)
}
// 2. 包装成 PendingIntent
val pendingIntent = PendingIntent.getActivity(
context,
REQUEST_CODE, // 区分不同的 PendingIntent
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 3. 用于通知
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("新消息")
.setContentIntent(pendingIntent) // 点击通知时执行
.setAutoCancel(true)
.build()
FLAG 详解
| Flag | 含义 |
|---|---|
FLAG_CANCEL_CURRENT |
取消已有的 PendingIntent,创建新的 |
FLAG_UPDATE_CURRENT |
保留已有的 PendingIntent,但更新 Extra 数据 |
FLAG_NO_CREATE |
如果不存在则返回 null,不创建 |
FLAG_ONE_SHOT |
只能使用一次,用后失效 |
FLAG_IMMUTABLE |
Android 12+ 推荐:PendingIntent 内容不可被接收方修改(安全) |
FLAG_MUTABLE |
允许接收方修改(如 Bubble 功能需要) |
Android 12(API 31)起,创建 PendingIntent 必须指定
FLAG_IMMUTABLE或FLAG_MUTABLE之一,否则抛出异常。
安全注意事项
PendingIntent 实质上是在你的应用权限下执行的 Intent。如果你创建了一个空 Intent 的 PendingIntent,并传给第三方应用,对方可以填入任意内容然后以你的身份执行——这是一个典型的安全漏洞(PendingIntent Hijacking)。
// ❌ 危险:空 Intent 的 PendingIntent
val pendingIntent = PendingIntent.getActivity(context, 0, Intent(), FLAG_MUTABLE)
// ✅ 安全:指定 ComponentName,使用 FLAG_IMMUTABLE
val intent = Intent(context, MyActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
生产高频考点
Q:隐式 Intent 的匹配流程?
系统遍历所有已安装应用的 Manifest,找到声明了匹配 IntentFilter 的组件。匹配需同时通过 Action、Category、Data 三项测试。若有多个匹配结果,系统弹出选择器(Chooser)让用户选择。
Q:PendingIntent 和 Intent 的区别?
Intent 是立即执行的意图,只能在有 Context 的情况下发送。PendingIntent 是「将来某时刻」由其他应用/系统代替你执行的意图,可以脱离你的应用上下文存在。
Q:同一个 Intent 被两个应用都能处理时,系统如何决定使用哪个?
系统会显示一个 Chooser(应用选择对话框)让用户选择。你也可以主动调用 Intent.createChooser() 强制显示选择器(即使只有一个匹配)。如果你想指定默认应用,可以使用 ComponentName 显式指定。
Q:Android 11 的包可见性(Package Visibility)限制对 Intent 有什么影响?
Android 11(API 30)起,应用默认只能「看到」满足特定条件的其他应用(自己安装的、系统应用等)。若要通过隐式 Intent 启动其他应用,需在 Manifest 中声明 <queries> 元素:
<queries>
<!-- 声明你需要查询的 Intent 类型 -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>