1 PendingIntent的底层实现原理
PendingIntent的本质是一个跨进程异步激发机制,其核心功能依赖于Android系统的AMS(Activity Manager Service)服务。与普通Intent不同,PendingIntent并不是立即执行某个行为,而是将执行意图"封装"起来,在满足特定条件或触发某些事件后才执行指定行为。
1.1 PendingIntent的内部结构
从源码角度看,PendingIntent的实现关键如下:
public final class PendingIntent implements Parcelable {
private final IIntentSender mTarget;
// 标志位定义
public static final int FLAG_ONE_SHOT = 1<<30;
public static final int FLAG_NO_CREATE = 1<<29;
public static final int FLAG_CANCEL_CURRENT = 1<<28;
public static final int FLAG_UPDATE_CURRENT = 1<<27;
// ... 其他代码
}
这里的mTarget是一个IIntentSender类型的Binder代理对象,它指向的是AMS中的PendingIntentRecord实体。这就是PendingIntent能够跨进程传递的关键——客户端持有的是代理对象,实际操作都会通过Binder调用到系统服务进程中的AMS。
1.2 AMS中的PendingIntent管理
在AMS中,每个PendingIntent都有一个对应的PendingIntentRecord对象:
class PendingIntentRecord extends IIntentSender.Stub {
final ActivityManagerService owner;
final Key key; // 关键标识
final int uid;
final WeakReference<PendingIntentRecord> ref;
boolean sent = false;
boolean canceled = false;
// ... 其他字段和方法
}
其中的Key内部类包含了创建PendingIntent时的重要信息:
type: 表示PendingIntent类型(Activity、Broadcast或Service)packageName: 包名activity: 关联的Activity记录requestCode: 请求代码requestIntent: 原始的Intent信息allIntents: 所有相关的Intent数组flags: 标志位
2 多次调用send()对Activity生命周期的影响
当多次调用PendingIntent.send()时,Activity的生命周期行为取决于多个因素,包括PendingIntent的标志位(Flags)、Activity的启动模式(launchMode),以及当前任务栈(Task)的状态。
2.1 常规情况下的生命周期调用
在普通情况下(没有特殊标志位,Activity使用standard启动模式),每次调用send()方法:
- 都会启动一个新的Activity实例
- 生命周期调用顺序为:
onCreate()→onStart()→onResume() - 之前的Activity实例依然保留在任务栈中
这种模式下,多次调用send()会导致任务栈中存在多个相同的Activity实例,用户按返回键时会按打开顺序逆向遍历这些实例。
2.2 使用FLAG_UPDATE_CURRENT的情况
当使用FLAG_UPDATE_CURRENT标志时:
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
这种情况下,如果PendingIntent已经存在,系统会保持原有的PendingIntent但更新其Intent中的额外数据(Extras)。但需要注意的是,对于Activity生命周期的影响取决于Activity的启动模式:
- standard模式(默认):仍然会创建新的Activity实例,但Intent中会包含更新后的Extras数据
- singleTop模式:如果Activity已在栈顶,会调用现有实例的
onNewIntent()方法并传入更新后的Intent;否则创建新实例 - singleTask/singleInstance模式:会调用现有实例的
onNewIntent()方法并传入更新后的Intent
2.3 使用FLAG_ONE_SHOT的情况
当使用FLAG_ONE_SHOT标志时:
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_ONE_SHOT
);
此标志表示PendingIntent只能使用一次。在第一次调用send()后,PendingIntent将自动失效,后续再次调用send()会失败(系统可能抛出异常或静默忽略,取决于调用方式)。
2.4 使用FLAG_CANCEL_CURRENT的情况
当使用FLAG_CANCEL_CURRENT标志时:
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_CANCEL_CURRENT
);
此标志表示先取消已有的PendingIntent再创建新的。对于Activity生命周期的影响是:如果原先的PendingIntent尚未触发,则会被取消;新创建的PendingIntent会正常触发Activity启动。
下面是不同标志位对多次send()调用影响的对比表格:
| 标志位 | 多次send()效果 | Activity生命周期影响 | 适用场景 |
|---|---|---|---|
| 无标志 | 每次send()都触发新Activity实例 | 每次都会调用onCreate()-onStart()-onResume() | 需要多个独立实例的场景 |
| FLAG_UPDATE_CURRENT | 更新Intent数据,保持PendingIntent | 取决于启动模式:standard创建新实例,singleTop/singleTask调用onNewIntent() | 更新通知内容的场景 |
| FLAG_ONE_SHOT | 只能使用一次,后续send()失败 | 只有第一次send()会触发生命周期 | 一次性操作(如验证码) |
| FLAG_CANCEL_CURRENT | 取消之前的,创建新的PendingIntent | 前一个未触发的会被取消,新的会触发生命周期 | 确保只有最新意图生效 |
3 Activity启动模式对生命周期的影响
Activity的启动模式(launchMode)对多次send()调用的生命周期行为有重要影响。
3.1 standard模式(默认)
在standard模式下,每次send()调用都会创建新的Activity实例,无论是否已存在相同类型的实例。生命周期流程如下:
这种模式下,任务栈中会积累多个相同Activity的实例。
3.2 singleTop模式
在singleTop模式下,行为有所不同:
- 如果Activity不在栈顶,会创建新实例,生命周期与standard模式相同
- 如果Activity已在栈顶,不会创建新实例,而是调用现有实例的
onNewIntent()方法
生命周期流程可能如下:
3.3 singleTask和singleInstance模式
在这两种模式下,系统只会存在一个Activity实例:
- 如果Activity尚未运行,第一次
send()会创建实例并调用完整生命周期 - 后续的
send()调用都会触发onNewIntent()方法而不是创建新实例 - 同时,任务栈中该Activity之上的所有其他Activity会被清空(clearTop语义)
4 源码层面的执行流程分析
4.1 send()方法的执行路径
当调用PendingIntent.send()时,实际的执行流程如下:
-
客户端调用:应用程序调用
PendingIntent.send()方法 -
Binder调用:通过
IIntentSender代理对象发起跨进程调用到AMS -
AMS处理:AMS找到对应的
PendingIntentRecord并处理请求 -
Intent发送:AMS根据PendingIntent的类型调用相应的方法:
- Activity:调用
startActivity()或相关方法 - Broadcast:调用
sendBroadcast()或相关方法 - Service:调用
startService()或相关方法
- Activity:调用
-
生命周期分发:AMS通过Binder调用回到客户端进程,由ActivityThread处理生命周期回调
4.2 关键源码分析
在AMS中,处理send请求的关键代码位于PendingIntentRecord.java的sendInner()方法:
int sendInner(int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission,
Bundle options) {
// 检查是否已取消
if (canceled) {
return 0;
}
// 根据flags处理Intent合并
if (intent != null) {
// 应用FLAG_UPDATE_CURRENT等标志的逻辑
if ((flags & FLAG_UPDATE_CURRENT) != 0) {
// 更新Intent的extras数据
try {
intent.replaceExtras(mKey.requestIntent);
} catch (RuntimeException e) {
// 异常处理
}
}
} else {
intent = mKey.requestIntent;
}
// 根据类型分发处理
switch (mKey.type) {
case INTENT_SENDER_ACTIVITY:
// 启动Activity的相关逻辑
return startActivity(intent, resolvedType, finishedReceiver,
requiredPermission, options);
case INTENT_SENDER_BROADCAST:
// 发送广播的相关逻辑
return sendBroadcast(intent, resolvedType, finishedReceiver,
requiredPermission, options);
case INTENT_SENDER_SERVICE:
// 启动服务的相关逻辑
return startService(intent, resolvedType, finishedReceiver,
requiredPermission, options);
}
return 0;
}
在客户端进程,Activity生命周期的回调是通过ActivityThread处理的:
public final class ActivityThread extends ClientTransactionHandler {
// 处理启动Activity的请求
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 创建Activity实例
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
// ...
} catch (Exception e) {
// 异常处理
}
// 调用生命周期方法
if (activity != null) {
// 调用onCreate()
activity.onCreate(r.state);
// 调用onStart()
activity.onStart();
// 其他生命周期处理
}
return activity;
}
// 处理onNewIntent回调
private void handleNewIntent(NewIntentData data) {
ActivityClientRecord r = mActivities.get(data.token);
if (r != null) {
r.activity.performNewIntent(data.intent);
}
}
}
5 开发者注意事项与最佳实践
5.1 避免Intent数据混淆
当使用FLAG_UPDATE_CURRENT时,需要注意系统判断Intent是否相同的标准。系统使用Intent.filterEquals()方法来比较Intent,该方法会比较Action、Data、Type、Component和Categories,但不比较Extras。这意味着:
- 只有上述属性相同的Intent才会被认为是"相同"的
- 如果希望更新Extras,必须确保这些属性保持不变
- 如果需要不同的Extras,应考虑使用不同的requestCode
5.2 正确处理onNewIntent()
对于可能被多次触发的Activity,应该正确处理onNewIntent()回调:
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 必须调用setIntent()更新Activity保存的Intent
setIntent(intent);
// 处理新的Intent数据
processIntentData(intent);
}
@Override
protected void onResume() {
super.onResume();
// 或者在这里处理Intent数据
processIntentData(getIntent());
}
5.3 合理使用requestCode
requestCode参数并不是"currently not used"(如某些文档所说),而是用于区分不同的PendingIntent。当需要多个不同的PendingIntent时,应该使用不同的requestCode:
// 不同的requestCode创建不同的PendingIntent
PendingIntent pendingIntent1 = PendingIntent.getActivity(context, 1, intent1, flags);
PendingIntent pendingIntent2 = PendingIntent.getActivity(context, 2, intent2, flags);
5.4 应对进程被杀的情况
当应用进程被系统杀死后重建时,AMS会重新传递最后一次Intent。这意味着:
6 总结
PendingIntent的多次send()调用对Activity生命周期的影响是一个复杂但重要的话题,涉及到AMS、Binder机制和Activity生命周期管理等多个方面。关键点包括:
- PendingIntent的核心是跨进程机制,通过IIntentSender代理与AMS中的PendingIntentRecord实体通信
- 标志位(Flags)决定行为:不同标志位对多次send()有截然不同的影响
- 启动模式(launchMode)影响实例管理:standard会创建多个实例,而singleTop/singleTask会复用实例
- 正确处理onNewIntent() 对于避免数据混淆至关重要
- 合理使用requestCode可以创建不同的PendingIntent实例
理解这些底层机制有助于开发出更加稳定和符合预期的Android应用,特别是在处理通知、闹钟等跨组件交互场景时。