PendingIntent机制

1.简介

Pending意思是待处理的、即将发生的,Intent则是Android中用于启动指定Activity、Service、Broadcast的意图信息类,连在一起表示一个将在未来某个时刻启动的意图,本质上是对Intent的封装。Android中PendingIntent主要用于拉起一个外部进程,或者不同进程间通信。具体场景是A进程作为发起端,它可以从系统“获取”一个PendingIntent,然后A进程可以将PendingIntent对象通过binder机制“传递”给B进程,再由B进程在未来某个合适时机,“回调”PendingIntent对象的send()动作,B进程可以通过PendingIntent携带额外信息通知或拉起已死亡的A进程的Activity、Service、Broadcast。

2.主要用途:传给外部应用用于通知自己或者拉起自己

  • AlarmManager定时任务
  • Notification通知点击
  • Widget桌面卡片
  • 应用间拉起(保活):两个APP可通过Binder传递自定义的PendingIntent对象,对方可在指定的某个逻辑时刻通知或拉起另一方,某些应用使用此特点进行业务进程保活。

3.使用

(1)获取PendingIntent API

PendingIntent.getActivity(...)
PendingIntent.getActivities(...)
PendingIntent.getBroadcast(...)
PendingIntent.getForegroundService(...)
PendingIntent.getService(Context, int, Intent, int)

(2)上述方法的flags参数
// 表示只能使用一次,使用后自动被cancel
public static final int FLAG_ONE_SHOT = 1<<30;
// 若新请求的PendingIntent发现已经存在,则继续使用已存在的,不存在则返回空
public static final int FLAG_NO_CREATE = 1<<29;
// 若新请求的PendingIntent已存在,则取消已存在的,并创建新PendingIntent替换
public static final int FLAG_CANCEL_CURRENT = 1<<28;
// 若新请求的PendingIntent已经存在,则都将被更新
public static final int FLAG_UPDATE_CURRENT = 1<<27;

Android12(Android S)及之后变更:PendingIntent必须声明可变性,即最后一个参数需带上新增的flag

// 表示PendingIntent对象在外部应用中不可被修改
public static final int FLAG_IMMUTABLE = 1<<26;
// PendingIntent.send()传入的intent内容可以被应用合并到PendingIntent中的 Intent
public static final int FLAG_MUTABLE = 1<<25; 
(3)使用举例

A进程发送方 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
A进程通过AIDL发送pendingIntent给B进程,并实现对应intent的广播接收消息
B进程接收方获取到pendingIntent后,调用pendingIntent.send(context, code, intent)即可触发A进程收到广播

(4)PendingIntent send方法

获取PendingIntent对象后通过调用send方法即可触发对应Intent意图,相当于startActivity()、startService()、sendBroadcast()
对于动态注册的广播也可收到。如果要取消 PendingIntent,可调用PendingIntent对象的cancel()方法。

(5)PendingIntent匹配规则

如果两个PendingIntent内部的Intent相同并且requestCode也相同那么这两个PendingIntent相同。这里的Intent相同指ConponentName和intent-filter都相同,extras不参与匹配。即Intent.filterEquals的比较。

4.原理

A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it.
通过PendingIntent的静态get方法获取到PendingIntent对象后,内部会通过Binder 通信将PendingIntent意图注册到AMS系统服务进程中,并获得一个Binder对象 IIntentSender封装在PendingIntent对象中返回。
PendingIntent.java

    public static PendingIntent getBroadcast(Context context, int requestCode,
            @NonNull Intent intent, @Flags int flags) {
        return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser());
    }

    public static PendingIntent getBroadcastAsUser(Context context, int requestCode,
            Intent intent, int flags, UserHandle userHandle) {
        String packageName = context.getPackageName();
        String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
        checkFlags(flags, packageName);
        try {
            intent.prepareToLeaveProcess(context);
            IIntentSender target =
                 // Binder通信注册Intent到AMS
                ActivityManager.getService().getIntentSenderWithFeature(
                    INTENT_SENDER_BROADCAST, packageName,
                    context.getAttributionTag(), null, null, requestCode, new Intent[] { intent },
                    resolvedType != null ? new String[] { resolvedType } : null,
                    flags, null, userHandle.getIdentifier());
            // 将获得的Binder对象IIntentSender封装在PendingIntent对象中返回
            return target != null ? new PendingIntent(target) : null;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

ActivityManagerService.java

// 运行在AMS
    public IIntentSender getIntentSenderWithFeatureAsApp(int type, String packageName,
            String featureId, IBinder token, String resultWho, int requestCode, Intent[] intents,
            String[] resolvedTypes, int flags, Bundle bOptions, int userId, int owningUid) {
        // NOTE: The service lock isn't held in this method because nothing in the method requires
        // the service lock to be held.
        ...
        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), owningUid, userId,
                type == ActivityManager.INTENT_SENDER_BROADCAST,
                ALLOW_NON_FULL, "getIntentSender", null);
        ...
            return mPendingIntentController.getIntentSender(type, packageName, featureId,
                    owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes,
                    flags, bOptions);
        } catch (RemoteException e) {
            throw new SecurityException(e);
        }
    }

PendingIntentController.java

    // 通过HashMap保存PendingIntent
    /** Set of IntentSenderRecord objects that are currently active. */
    final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords
            = new HashMap<>();

    public PendingIntentRecord getIntentSender(int type, String packageName,
            @Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions) {
        synchronized (mLock) {
            if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSender(): uid=" + callingUid);

            // We're going to be splicing together extras before sending, so we're
            // okay poking into any contained extras.
            if (intents != null) {
                for (int i = 0; i < intents.length; i++) {
                    intents[i].setDefusable(true);
                }
            }
            Bundle.setDefusable(bOptions, true);

            final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
            final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
            final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0;
            flags &= ~(PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_CANCEL_CURRENT
                    | PendingIntent.FLAG_UPDATE_CURRENT);

            PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,
                    token, resultWho, requestCode, intents, resolvedTypes, flags,
                    SafeActivityOptions.fromBundle(bOptions), userId);
            ...
            rec = new PendingIntentRecord(this, key, callingUid);
            mIntentSenderRecords.put(key, rec.ref);
            incrementUidStatLocked(rec);
            return rec;
        }
    }

其它进程需要触发延时意图时通过PendingIntent#send()

    public int sendAndReturnResult(Context context, int code, @Nullable Intent intent,
            @Nullable OnFinished onFinished, @Nullable Handler handler,
            @Nullable String requiredPermission, @Nullable Bundle options)
            throws CanceledException {
        ...
        return ActivityManager.getService().sendIntentSender(
                mTarget, mWhitelistToken, code, intent, resolvedType,
                onFinished != null
                        ? new FinishedDispatcher(this, onFinished, handler)
                        : null,
                requiredPermission, options);
    }

ActivityManagerService.java
最终会通过AMS拉起对应组件

// 运行在AMS
    public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,
            Intent intent, String resolvedType,
            IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
        if (target instanceof PendingIntentRecord) {
            return ((PendingIntentRecord)target).sendWithResult(code, intent, resolvedType,
                    allowlistToken, finishedReceiver, requiredPermission, options);
        } else {
        ...
    }

5.安全性

上述操作A进程给B进程发送PendingIntent对象,B进程可通过此拉起A进程,操作上看起来也可通过自己封装Intent对象传递给B进程。这里PendingIntent除了做Intent这样的封装外,通过AMS将A进程的进程id也存在了AMS中,当B进程PendingIntent.send拉起A进程时,走到AMS实际体现在以A进程的身份拉起自己,通过AMS控制从而保证了拉起A进程组件的安全性,从而A进程的组件不用设置为exported也能被拉起,故不用外部暴露。

6.总结

PendingIntent的优势主要体现在延迟拉起Activity、Service、Broadcast,即使对方进程已死亡,可用于拉起对方进程进行业务,完成预定操作,如拉起下拉通知对应的Activity或通过AlarmManager拉起一个定时闹钟页面,同时保证了拉起操作的安全性。

参考:

PendingIntent官网介绍
Android PendingIntent
关于PendingIntent需要知道的事
PendingIntent和Intent区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容