美文网首页
PendingIntent机制

PendingIntent机制

作者: 文泰ChrisTwain | 来源:发表于2023-01-02 21:37 被阅读0次

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

    相关文章

      网友评论

          本文标题:PendingIntent机制

          本文链接:https://www.haomeiwen.com/subject/ekxwfdtx.html