首页 > 代码库 > 【从源代码看Android】05 PendingIntent

【从源代码看Android】05 PendingIntent

一、引入

PendingIntent是一个非常不起眼的类,

你可能在以下情况下遇到过它


1、AlarmManager

int requestID = 1;
        AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
        Intent i = new Intent(this,AshqalReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this
                ,requestID
                ,i
                ,PendingIntent.FLAG_UPDATE_CURRENT);
        am.setRepeating(AlarmManager.RTC_WAKEUP
                , System.currentTimeMillis() + 1000
                , 1000
                , pi);


2、NotificationManager

//初始化
int requestID = 1;
        mPendingIntent = PendingIntent.getActivity(this
                ,requestID
                ,new Intent(this,MyActivity.class)
                ,PendingIntent.FLAG_UPDATE_CURRENT);


//调用
NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.drawable.ic_launcher);
        builder.setContentTitle("Notification Title");
        builder.setContentText("context text");
        builder.setContentIntent(mPendingIntent);
        //builder.setAutoCancel(true);

        Notification notification = builder.build();
        nm.notify(0,notification);

或者

PendingIntent mPendingIntent = PendingIntent.getService(mCtx
                    ,requestID
                    ,mIntent
                    ,PendingIntent.FLAG_UPDATE_CURRENT);
            mBuilder.setContentIntent(mPendingIntent);


2种方式都是将想要的操作(调用broadcast、打开activity、打开service)保存到PendingIntent中,在合适的时候执行这个操作

不管从下拉消息栏打开activity或者打开service操作,还是通过AlarmManager定时广播的方式,

都是跨进程的方式下进行的,当前的PendingIntent对象都保存在了哪?如何在其他进程中调用到PendingIntent?

这得从PendingIntent的创建开始讲



二、PendingIntent创建

前面提到了3种静态的方式创建,按照android sdk reference里的描述,分别是

PendIntent.getService,PendingIntent.getActivity,PendingIntent.getBroadcast

这三种创建方法很类似,我们只挑选其中PendIntent.getService进行


创建示例代码如下:

PendingIntent mPendingIntent = PendingIntent.getService(mCtx
                    ,requestID
                    ,mIntent
                    ,PendingIntent.FLAG_UPDATE_CURRENT);

如果需要之后的操作是startService,那么就需要从PendingIntent.getService创建PendingIntent


//android.app.PendingIntent.java,getService函数实现

public static PendingIntent getService(Context context, int requestCode,
            Intent intent, int flags) {
        String packageName = context.getPackageName();
        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
                context.getContentResolver()) : null;
        try {
            intent.prepareToLeaveProcess();
            IIntentSender target =
                ActivityManagerNative.getDefault().getIntentSender(
                    ActivityManager.INTENT_SENDER_SERVICE, packageName,
                    null, null, requestCode, new Intent[] { intent },
                    resolvedType != null ? new String[] { resolvedType } : null,
                    flags, null, UserHandle.myUserId());
            return target != null ? new PendingIntent(target) : null;
        } catch (RemoteException e) {
        }
        return null;
    }

从context获取到包名和Intent的MIME类型(如text/plain),

使用静态函数ActivityManagerNative.getDefault() 获取到ActivityManagerService的本地代理对象ActivityManagerProxy(其中封装了远程的ActivityManagerService)

调用这个ActivityManagerProxy的getIntentSender函数


//android.app.ActivityManagerNative.ActivityManagerProxy

    public IIntentSender getIntentSender(int type,
            String packageName, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
            Bundle options, int userId) throws RemoteException {
        Parcel data = http://www.mamicode.com/Parcel.obtain();>
很明显,ActivityManagerProxy将一系列参数数据序列化到一个Parcel对象,然后调用mRemote(远程的ActivityManagerService对象)的transact方法,

告诉在System进程的ActivityManagerService处理这个事物



在System进程的ActivityManagerService处理这个事物代码

//android.app.ActivityManagerNative.onTransact方法片段,

//因为ActivityManagerService继承自ActivityManagerNative,

//ActivityManagerService将此请求放到ActivityManagerNative处理

case GET_INTENT_SENDER_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            int type = data.readInt();
            String packageName = data.readString();
            IBinder token = data.readStrongBinder();
            String resultWho = data.readString();
            int requestCode = data.readInt();
            Intent[] requestIntents;
            String[] requestResolvedTypes;
            if (data.readInt() != 0) {
                requestIntents = data.createTypedArray(Intent.CREATOR);
                requestResolvedTypes = data.createStringArray();
            } else {
                requestIntents = null;
                requestResolvedTypes = null;
            }
            int fl = data.readInt();
            Bundle options = data.readInt() != 0
                    ? Bundle.CREATOR.createFromParcel(data) : null;
            int userId = data.readInt();
            IIntentSender res = getIntentSender(type, packageName, token,
                    resultWho, requestCode, requestIntents,
                    requestResolvedTypes, fl, options, userId);
            reply.writeNoException();
            reply.writeStrongBinder(res != null ? res.asBinder() : null);
            return true;
        }

将data反序列化获得数据,调用ActivityManagerService的getIntentSender方法,然后把回馈写入reply,返回从mRemote.transcat函数返回


我们深入分析下ActivityManagerService的getIntentSender方法,如下

@Override
    public IIntentSender getIntentSender(int type,
            String packageName, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes,
            int flags, Bundle options, int userId) {
        enforceNotIsolatedCaller("getIntentSender");
        // Refuse possible leaked file descriptors
        if (intents != null) {
            if (intents.length < 1) {
                throw new IllegalArgumentException("Intents array length must be >= 1");
            }
            for (int i=0; i<intents.length; i++) {
                Intent intent = intents[i];
                if (intent != null) {
                    if (intent.hasFileDescriptors()) {
                        throw new IllegalArgumentException("File descriptors passed in Intent");
                    }
                    if (type == ActivityManager.INTENT_SENDER_BROADCAST &&
                            (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
                        throw new IllegalArgumentException(
                                "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
                    }
                    intents[i] = new Intent(intent);
                }
            }
            if (resolvedTypes != null && resolvedTypes.length != intents.length) {
                throw new IllegalArgumentException(
                        "Intent array length does not match resolvedTypes length");
            }
        }
        if (options != null) {
            if (options.hasFileDescriptors()) {
                throw new IllegalArgumentException("File descriptors passed in options");
            }
        }
        
        synchronized(this) {
            int callingUid = Binder.getCallingUid();
            int origUserId = userId;
            userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
                    type == ActivityManager.INTENT_SENDER_BROADCAST, false,
                    "getIntentSender", null);
            if (origUserId == UserHandle.USER_CURRENT) {
                // We don't want to evaluate this until the pending intent is
                // actually executed.  However, we do want to always do the
                // security checking for it above.
                userId = UserHandle.USER_CURRENT;
            }
            try {
                if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
                    int uid = AppGlobals.getPackageManager()
                            .getPackageUid(packageName, UserHandle.getUserId(callingUid));
                    if (!UserHandle.isSameApp(callingUid, uid)) {
                        String msg = "Permission Denial: getIntentSender() from pid="
                            + Binder.getCallingPid()
                            + ", uid=" + Binder.getCallingUid()
                            + ", (need uid=" + uid + ")"
                            + " is not allowed to send as package " + packageName;
                        Slog.w(TAG, msg);
                        throw new SecurityException(msg);
                    }
                }

                return getIntentSenderLocked(type, packageName, callingUid, userId,
                        token, resultWho, requestCode, intents, resolvedTypes, flags, options);
                
            } catch (RemoteException e) {
                throw new SecurityException(e);
            }
        }
    }

首先深拷贝了整组intent,

然后通过传递过来的信息检查callingUid和packageName对应的uid是否相同,

然后调用到函数getIntentSendrLocked中


//ActivityManagerService的getIntentSendrLocked方法

IIntentSender getIntentSenderLocked(int type, String packageName,
            int callingUid, int userId, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
            Bundle options) {
        if (DEBUG_MU)
            Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
        ActivityRecord activity = null;
        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
            activity = ActivityRecord.isInStackLocked(token);
            if (activity == null) {
                return null;
            }
            if (activity.finishing) {
                return null;
            }
        }

        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, activity, resultWho,
                requestCode, intents, resolvedTypes, flags, options, userId);
        WeakReference<PendingIntentRecord> ref;
        ref = mIntentSenderRecords.get(key);
        PendingIntentRecord rec = ref != null ? ref.get() : null;
        if (rec != null) {
            if (!cancelCurrent) {
                if (updateCurrent) {
                    if (rec.key.requestIntent != null) {
                        rec.key.requestIntent.replaceExtras(intents != null ?
                                intents[intents.length - 1] : null);
                    }
                    if (intents != null) {
                        intents[intents.length-1] = rec.key.requestIntent;
                        rec.key.allIntents = intents;
                        rec.key.allResolvedTypes = resolvedTypes;
                    } else {
                        rec.key.allIntents = null;
                        rec.key.allResolvedTypes = null;
                    }
                }
                return rec;
            }
            rec.canceled = true;
            mIntentSenderRecords.remove(key);
        }
        if (noCreate) {
            return rec;
        }
        rec = new PendingIntentRecord(this, key, callingUid);
        mIntentSenderRecords.put(key, rec.ref);
        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
            if (activity.pendingResults == null) {
                activity.pendingResults
                        = new HashSet<WeakReference<PendingIntentRecord>>();
            }
            activity.pendingResults.add(rec.ref);
        }
        return rec;
    }


这里使用到了刚刚参数中的3个Flags

FLAG_NO_CREATE,不存在就创建,存在就删除后再创建,一次性使用,不会放到mIntentSenderRecords中

FLAG_CANCEL_CURRENT,存在就删除当前的,再创建新的放入mIntentSenderRecords中

FLAG_UPDATE_CURRENT,存在且不为cancel则替换intent,否则创建新的,放入mIntentSenderRecords中

读取到值后将三个flag关闭

之后用传递过来的多个参数组成一个PendingIntentRecord.Key,然后在mIntentSenderRecords容器中寻找这个key是否存在

按上述3个Flag逻辑执行

最后都返回一个PendingIntentRecord,

这个PendingIntentRecord继承自aidl接口IIntentSender.Stub,实现了send方法


最后把获取到的PendingIntentRecord通过序列化方式存到reply中,

通过binder机制进程间通讯,把数据回传给用户空间

使用PendingIntent封装这个IIntentSender,new PendingIntent(IIntentSender)

这样PendingIntent就创建完成了


因为PendingIntentRecord.Key重写了自己的hashCode和equals方法,

所以即使对象的PendingIntent内存地址不同,也有可能被判定为相同的请求



二、PendingIntent的使用

因为把PendingIntent对象传递给了目标(如顶栏通知栏、广播),

如在AlarmManager中把传递给AlarmManager的PendingIntent对象封装成了Alarm对象

Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                operation, workSource);

然后在恰当的时机对PendingIntent对象调用send方法

//com.android.server.AlarmManagerService.AlarmHander.handlerMessage

public void handleMessage(Message msg) {
            if (msg.what == ALARM_EVENT) {
                ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
                synchronized (mLock) {
                    final long nowRTC = System.currentTimeMillis();
                    final long nowELAPSED = SystemClock.elapsedRealtime();
                    triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
                }
                
                // now trigger the alarms without the lock held
                for (int i=0; i<triggerList.size(); i++) {
                    Alarm alarm = triggerList.get(i);
                    try {
                        alarm.operation.send();
                    } catch (PendingIntent.CanceledException e) {
                        if (alarm.repeatInterval > 0) {
                            // This IntentSender is no longer valid, but this
                            // is a repeating alarm, so toss the hoser.
                            remove(alarm.operation);
                        }
                    }
                }
            }
        }

就可以触发PendingIntentRecoard的sendInner函数,

例如类型为service的,调用如下函数片段

case ActivityManager.INTENT_SENDER_SERVICE:
                        try {
                            owner.startServiceInPackage(uid,
                                    finalIntent, resolvedType, userId);
                        } catch (RuntimeException e) {
                            Slog.w(ActivityManagerService.TAG,
                                    "Unable to send startService intent", e);
                        }
                        break;

startServiceInPackage,这样就可以启动service了



四、启发

受此启发,因为PendingIntent继承自Parcelable可以被序列化

我们只要把PendingIntent通过Intent序列化给其他相同package的组件中,

反序列化回来,调用send,就能触发预定义的操作

具体方式参见:how-to-use-pendingintent-to-communicate-from-a-service-to-a-client-activity

http://stackoverflow.com/questions/6099364/how-to-use-pendingintent-to-communicate-from-a-service-to-a-client-activity



【从源代码看Android】05 PendingIntent