PendingIntent机制

2023-01-02  本文已影响0人  文泰ChrisTwain

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.主要用途:传给外部应用用于通知自己或者拉起自己

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区别

上一篇 下一篇

猜你喜欢

热点阅读