🔍 PendingIntent的跨进程机制与Activity生命周期分析

62 阅读8分钟

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()方法:

  1. 都会启动一个新的Activity实例
  2. 生命周期调用顺序为:onCreate() → onStart() → onResume()
  3. 之前的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实例,无论是否已存在相同类型的实例。生命周期流程如下:

deepseek_mermaid_20250909_d181ea.png

这种模式下,任务栈中会积累多个相同Activity的实例。

3.2 singleTop模式

在singleTop模式下,行为有所不同:

  • 如果Activity不在栈顶,会创建新实例,生命周期与standard模式相同
  • 如果Activity已在栈顶,不会创建新实例,而是调用现有实例的onNewIntent()方法

生命周期流程可能如下:

deepseek_mermaid_20250909_2715d0.png

3.3 singleTask和singleInstance模式

在这两种模式下,系统只会存在一个Activity实例:

  1. 如果Activity尚未运行,第一次send()会创建实例并调用完整生命周期
  2. 后续的send()调用都会触发onNewIntent()方法而不是创建新实例
  3. 同时,任务栈中该Activity之上的所有其他Activity会被清空(clearTop语义)

4 源码层面的执行流程分析

4.1 send()方法的执行路径

当调用PendingIntent.send()时,实际的执行流程如下:

  1. 客户端调用:应用程序调用PendingIntent.send()方法

  2. Binder调用:通过IIntentSender代理对象发起跨进程调用到AMS

  3. AMS处理:AMS找到对应的PendingIntentRecord并处理请求

  4. Intent发送:AMS根据PendingIntent的类型调用相应的方法:

    • Activity:调用startActivity()或相关方法
    • Broadcast:调用sendBroadcast()或相关方法
    • Service:调用startService()或相关方法
  5. 生命周期分发:AMS通过Binder调用回到客户端进程,由ActivityThread处理生命周期回调

4.2 关键源码分析

在AMS中,处理send请求的关键代码位于PendingIntentRecord.javasendInner()方法:

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。这意味着:

  1. Activity会以最后一次的Intent被重建
  2. 可能需要保存额外状态来区分是否是重建情况
  3. 可以考虑使用onSaveInstanceState()保存状态

6 总结

PendingIntent的多次send()调用对Activity生命周期的影响是一个复杂但重要的话题,涉及到AMS、Binder机制和Activity生命周期管理等多个方面。关键点包括:

  1. PendingIntent的核心是跨进程机制,通过IIntentSender代理与AMS中的PendingIntentRecord实体通信
  2. 标志位(Flags)决定行为:不同标志位对多次send()有截然不同的影响
  3. 启动模式(launchMode)影响实例管理:standard会创建多个实例,而singleTop/singleTask会复用实例
  4. 正确处理onNewIntent() 对于避免数据混淆至关重要
  5. 合理使用requestCode可以创建不同的PendingIntent实例

理解这些底层机制有助于开发出更加稳定和符合预期的Android应用,特别是在处理通知、闹钟等跨组件交互场景时。