天天看點

【從源代碼看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 = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeInt(type);
        data.writeString(packageName);
        data.writeStrongBinder(token);
        data.writeString(resultWho);
        data.writeInt(requestCode);
        if (intents != null) {
            data.writeInt(1);
            data.writeTypedArray(intents, 0);
            data.writeStringArray(resolvedTypes);
        } else {
            data.writeInt(0);
        }
        data.writeInt(flags);
        if (options != null) {
            data.writeInt(1);
            options.writeToParcel(data, 0);
        } else {
            data.writeInt(0);
        }
        data.writeInt(userId);
        mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0);
        reply.readException();
        IIntentSender res = IIntentSender.Stub.asInterface(
            reply.readStrongBinder());
        data.recycle();
        reply.recycle();
        return res;
    }
           

很明顯,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

繼續閱讀