什么是 PendingIntent?它与普通 Intent 在权限和使用场景上有何不同?

解读

面试官抛出此题,核心想验证三件事:

  1. 你是否真正理解 PendingIntent 的“延迟授权”本质,而不是把它当成“可以延迟启动的 Intent”;
  2. 能否把“跨进程授权”与 Android 的沙箱/权限模型联系起来,说明为什么 AlarmManager、Notification、AppWidget 等系统组件只接受 PendingIntent;
  3. 能否结合国内厂商 ROM 的定制行为(如后台启动限制、对齐唤醒)给出落地经验,证明你在真实项目里用过,而不是背概念。

知识点

  1. 定义:PendingIntent 是系统持有的、封装了“启动组件动作 + 身份令牌”的 Parcelable 对象,应用进程被杀后仍可被系统在安全上下文里重新发射。
  2. 身份切换:普通 Intent 的发起方 UID 就是当前进程 UID;PendingIntent 被系统创建时会记录创建者 UID/PID 及签名级权限,发射时由系统代持创建者身份,目标组件看到的仍是原始创建者。
  3. 授权范围:创建时可附加 FLAG_IMMUTABLE/FLAG_MUTABLE、FLAG_ONE_SHOT、FLAG_CANCEL_CURRENT 等标记,精确控制重用、篡改与生命周期;普通 Intent 无法提供这种“一次性令牌”能力。
  4. 使用场景:
    • 通知点击、通知按钮、直接回复、气泡通知(Android 11+)
    • AlarmManager / WorkManager 的精确闹钟、延迟任务
    • AppWidget/Shortcut/Slice 的远程回调
    • 系统悬浮窗、画中画、车载 AAOS 的外部控制
    • 国内厂商推送通道(华为 HMS、OPPO Push、小米 Push)的点击回执
  5. 安全限制:
    • targetSdk 31+ 必须显式声明 PendingIntent 可变性(IMMUTABLE/MUTABLE),否则构建抛异常;
    • 若 PendingIntent 里包含有 URI 权限的 Intent,需同时授予 FLAG_GRANT_READ_URI_PERMISSION,并保证 URI 在发射瞬间仍有效;
    • 国内 ROM 对“后台启动 Activity”做了强管控,PendingIntent 启动前台服务或 Activity 需加 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK,并配合 <activity> 的 android:exported、android:visibleToInstantApps 属性,否则触发“Background activity start blocked”弹窗。
  6. 性能陷阱:
    • 每次获取 PendingIntent 都传相同 requestCode 但不同 Extra,系统会缓存旧 Intent,导致数据不更新;应使用 FLAG_CANCEL_CURRENT 或 FLAG_UPDATE_CURRENT;
    • 国内推送合并通道后,若把业务参数塞在 Extra 里,极易出现“点击通知参数丢失”,需把业务 ID 写进 data URI 而非 Extra,避免缓存命中错误。

答案

PendingIntent 是 Android 系统提供的“延迟授权令牌”,它在创建时被系统记录在 PendingIntentRecord 中,保存了原始应用的 UID、签名、Intent 及控制标记。与普通 Intent 相比,核心差异体现在三点:

  1. 发起身份:普通 Intent 只能由当前进程实时发起,目标组件看到的调用方就是当前 UID;PendingIntent 由系统代持原始创建者身份,即使创建进程已死,系统仍能以原 UID 启动目标组件,实现“跨进程代发”。
  2. 权限边界:普通 Intent 无法把自身权限授予他人;PendingIntent 通过 FLAG_GRANT_READ_URI_PERMISSION 等标记,可把 URI 临时访问权限授予外部应用,且权限随 PendingIntent 一起失效,天然适合一次性授权。
  3. 使用场景:普通 Intent 用于进程内或即时启动;PendingIntent 专用于“未来由系统或其他应用代我执行”的场景,如 Notification 点击、AlarmManager 定时、AppWidget 回调、车载桌面卡片、国内厂商推送消息回执等。
    实战注意:Android 12 后必须显式指定 IMMUTABLE/MUTABLE,否则抛 IllegalArgumentException;国内 ROM 对后台启动限制极严,PendingIntent 启动 Activity 需加 NEW_TASK 且目标 Activity 必须 exported=true,否则会被系统拦截并记录到 DropBox,影响线上灰度。

拓展思考

  1. 如何构造一个“仅允许系统发射一次、且携带敏感文件 URI”的 PendingIntent?
    答:使用 FLAG_ONE_SHOT | FLAG_IMMUTABLE | FLAG_GRANT_READ_URI_PERMISSION,并将 URI 加入 ClipData,确保系统只在第一次发射时授予临时权限,避免重放攻击。
  2. 国内推送合并通道后,厂商 SDK 把多个 App 的 PendingIntent 缓存在同一进程,如何防止“串号”?
    答:把业务主键写入 data URI(如 content://auth/item/id)而非 Intent Extra,利用 URI 参与缓存 key 的特性,保证每个通知对应唯一 PendingIntent;同时 requestCode 采用业务哈希,避免系统缓存复用。
  3. 车载场景下,系统桌面以系统 UID 发送 PendingIntent 给第三方车载 App,如何验证来源可信?
    答:在创建 PendingIntent 时通过 setCreatorPackage 限定包名,并在接收端使用 Activity.recreate() 重新检测 getLaunchedFromUid() 与 getLaunchedFromPackage(),对比系统签名,防止恶意 App 构造钓鱼 PendingIntent。