Android 面試題:說(shuō)一下 PendingIntent 和 Intent 的區(qū)別

請(qǐng)點(diǎn)贊篱蝇,你的點(diǎn)贊對(duì)我意義重大姑原,滿(mǎn)足下我的虛榮心催式。

?? Hi函喉,我是小彭。本文已收錄到 GitHub · Android-NoteBook 中荣月。這里有 Android 進(jìn)階成長(zhǎng)知識(shí)體系管呵,有志同道合的朋友,歡迎跟我一起成長(zhǎng)哺窄。

前言

  • 從字面意思上理解捐下,PendingIntent 是一種延遲的 Intent账锹,表示一種延遲執(zhí)行的意圖操作。對(duì)坷襟,但又不完全對(duì)奸柬。 一句話(huà)概括,PendingIntent 一種是支持授權(quán)其他應(yīng)用以當(dāng)前應(yīng)用的身份執(zhí)行包裝的 Intent 操作的系統(tǒng)特性啤握。
  • 在這篇文章里鸟缕,我將帶你理解 PendingIntent 的使用方法、設(shè)計(jì)理念以及核心源碼分析排抬,相信閱讀完這篇文章后你對(duì) PendingIntent 的理解將超過(guò)絕大部分同學(xué)懂从。如果能把幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注蹲蒲,你的支持對(duì)我非常重要番甩。

1. 認(rèn)識(shí) PendingIntent

1.1 為什么要使用 PendingIntent?

PendingIntent 的應(yīng)用場(chǎng)景關(guān)鍵在于間接的 Intent 跳轉(zhuǎn)需求届搁, 即先通過(guò)一級(jí) Intent 跳轉(zhuǎn)到某個(gè)組件缘薛,在該組件完成任務(wù)后再間接地跳轉(zhuǎn)到二級(jí)的 Intent。PendingIntent 中的單詞 “pending” 指延遲或掛起卡睦,就是指它是延遲的或掛起的宴胧。例如,你在以下場(chǎng)景中就可以使用 PendingIntent:

  • 場(chǎng)景 1 - 系統(tǒng)通知消息的點(diǎn)擊操作
  • 場(chǎng)景 2 - 桌面微件的點(diǎn)擊操作
  • 場(chǎng)景 3 - 系統(tǒng)鬧鐘操作
  • 場(chǎng)景 4 - 第三方應(yīng)用回調(diào)操作

可以看到表锻,在這些場(chǎng)景中恕齐,我們真正感興趣的操作是掛起的,并且該操作并不是由當(dāng)前應(yīng)用執(zhí)行瞬逊,而是由某個(gè)外部應(yīng)用來(lái) “間接” 執(zhí)行的显歧。例如,我們?cè)诎l(fā)送系統(tǒng)通知消息時(shí)确镊,會(huì)通過(guò) PendingIntent 構(gòu)造一個(gè)系統(tǒng)通知 Notification 士骤,并調(diào)用 NotificationManagerCompat.notify(…) 發(fā)送通知,此時(shí)并不會(huì)直接執(zhí)行 PendingIntent蕾域。而是當(dāng)系統(tǒng)顯示通知拷肌,并且用戶(hù)點(diǎn)擊通知時(shí),才會(huì)由系統(tǒng)通知這個(gè)系統(tǒng)應(yīng)用間接執(zhí)行 PendingIntent#send() 旨巷,而不是通過(guò)當(dāng)前應(yīng)用執(zhí)行廓块。

當(dāng)然,在低版本系統(tǒng)中契沫,你還可以使用嵌套 Intent(Intent#extra 中嵌套另一個(gè) Intent)來(lái)實(shí)現(xiàn)以上需求带猴。但是從 Android 12 開(kāi)始,嵌套 Intent 將被嚴(yán)格禁止懈万,原因下文會(huì)說(shuō)拴清。

1.2 PendingIntent 和 Intent 有什么區(qū)別靶病?

從結(jié)構(gòu)上來(lái)說(shuō),PendingIntent 是 Intent 的包裝類(lèi)口予,其內(nèi)部持有一個(gè)代表最終意圖操作的 Intent(事實(shí)上娄周,內(nèi)部是通過(guò) IIntentSender 間接持有)。它們的區(qū)別我認(rèn)為可以概括為 3 個(gè)維度:

  • 1沪停、執(zhí)行進(jìn)程不同 —— PendingIntent 在其他進(jìn)程執(zhí)行: Intent 通常會(huì)在創(chuàng)建進(jìn)程中執(zhí)行煤辨,而 PendingIntent 通常不會(huì)在創(chuàng)建進(jìn)程中執(zhí)行;
  • 2木张、執(zhí)行時(shí)間不同 —— PendingIntent 會(huì)延遲執(zhí)行: Intent 通常會(huì)立即執(zhí)行众辨,而 PendingIntent 通常會(huì)延遲執(zhí)行,延遲到其他進(jìn)程完成任務(wù)后再執(zhí)行舷礼,甚至延遲到創(chuàng)建進(jìn)程消亡后鹃彻。例如,在 場(chǎng)景 1 - 系統(tǒng)通知消息的點(diǎn)擊操作 中妻献,即使發(fā)送系統(tǒng)通知消息的進(jìn)程已經(jīng)消亡了蛛株,依然不妨礙二級(jí) Intent 的跳轉(zhuǎn);
  • 3育拨、執(zhí)行身份不同 —— PendingIntent 支持授權(quán): PendingIntent 內(nèi)部持有授權(quán)信息谨履,支持其他應(yīng)用以當(dāng)前應(yīng)用的身份執(zhí)行,這有利于避免嵌套 Intent 存在的安全隱患熬丧。而直接使用 Intent 的話(huà)笋粟,一般只能以當(dāng)前應(yīng)用的身份執(zhí)行(為什么說(shuō)一般?因?yàn)橛?Activity#startActivityAsUser() 這個(gè) API锹引,但一般你拿不到所需的參數(shù))。

提示: 當(dāng)然了唆香,如果你創(chuàng)建 PendingIntent 后又馬上同步地在當(dāng)前進(jìn)程消費(fèi)這個(gè) PendingIntent嫌变,那么時(shí)間維度上就沒(méi)區(qū)別了。但是這樣做其實(shí)不符合 PendingIntent 的應(yīng)用場(chǎng)景躬它。

1.3 嵌套 Intent 存在的安全隱患

上文提到腾啥,在低版本系統(tǒng)中,你可以使用嵌套 Intent 實(shí)現(xiàn)類(lèi)似于 PendingIntent 的需求冯吓。但這一方案從 Android 12 開(kāi)始被嚴(yán)格禁止倘待,為什么呢 —— 存在安全隱患。

舉個(gè)例子组贺,我們將啟動(dòng) ClientCallbackActivity 的 Intent 嵌套到啟動(dòng) ApiService 的 Intent 里凸舵,實(shí)現(xiàn)一個(gè) 場(chǎng)景 4 - 第三方應(yīng)用回調(diào)操作 的效果:

  • 步驟 1: Client App 請(qǐng)求 Provider App 的一個(gè)服務(wù)(這通過(guò)一級(jí) Intent 實(shí)現(xiàn));
  • 步驟 2: Provider App 在任務(wù)結(jié)束后回調(diào)到 Client App 的 ClientCallbackActivity(這通過(guò)嵌套的二級(jí) Intent 實(shí)現(xiàn))失尖。

該過(guò)程用示意圖表示如下:

乍看起來(lái)沒(méi)有問(wèn)題啊奄,但其實(shí)存在 2 個(gè)隱蔽的安全隱患:

  • 隱患 1 - Client App: 由于 ClientCallbackActivity 是從另一個(gè)應(yīng)用 Provider App 啟動(dòng)的渐苏,因此該 Activity 必須暴露為 exported。這意味著除了 Provider App 可以啟動(dòng)該 Activity 外菇夸,同時(shí)也給了惡意應(yīng)用啟動(dòng)該 Activity 的可能性琼富。如果 ClientCallbackActivity 是一個(gè)普通的 Activity 還要說(shuō),要是 ClientCallbackActivity 是一個(gè)敏感或高風(fēng)險(xiǎn)的行為(例如支付回調(diào))庄新,那么這就存在很大的安全隱患了鞠眉;
  • 隱患 2 - Provider App: 由于嵌套的 Intent 是在 Provider App 的上下文中啟動(dòng)的,那么二級(jí) Intent 不僅可以正常啟動(dòng) Client App 中的 ClientCallbackActivity(打開(kāi) exported 時(shí))择诈,還可以啟動(dòng) Provider App 中任意 Activity械蹋。這意味著給了惡意應(yīng)用啟動(dòng) Provider App 中敏感或高風(fēng)險(xiǎn)的 Activity 的可能性,即使這個(gè)敏感的 Activity 事先已經(jīng)關(guān)閉 exported吭从。這說(shuō)明 exported 機(jī)制失效了朝蜘,也存在很大的安全隱患。

該攻擊過(guò)程用示意圖表示如下:

解決方法是使用 PendingIntent 代替嵌套 Intent涩金,此時(shí)這兩個(gè)風(fēng)險(xiǎn)都不存在谱醇。為什么呢?—— 因?yàn)?PendingIntent 將以 Client App(PendingIntent 的創(chuàng)建進(jìn)程)的身份執(zhí)行步做,而不是 Provider App (PendingIntent 的消費(fèi)進(jìn)程)的身份執(zhí)行副渴。

現(xiàn)在,我們?cè)倩仡櫹逻€有沒(méi)有安全隱患:

  • 隱患 1 - Client App: 由于 PendingIntent 使用 Client App 的身份執(zhí)行全度,那么 ClientCallbackActivity 不再需要暴露為 exported煮剧。此時(shí),惡意應(yīng)用不存在常規(guī)啟動(dòng) ClientCallbackActivity 的可能性将鸵,風(fēng)險(xiǎn)解除勉盅;
  • 隱患 2 - Provider App: 由于 PendingIntent 使用 Client App / Attacker App 的身份執(zhí)行,而它們是沒(méi)有權(quán)限訪(fǎng)問(wèn) Provider App 非 exported 的 ApiSensitiveActivity 的顶掉。此時(shí)草娜,惡意應(yīng)用不能啟動(dòng) ApiSensitiveActivity,風(fēng)險(xiǎn)解除痒筒。

該過(guò)程用示意圖表示如下:

提示: 擔(dān)心有的同學(xué)鉆牛角這里再補(bǔ)充一下:如果我的二級(jí) Intent 就是想要回調(diào)到 Provider App 中的 ApiSensitiveActivity 那怎么辦宰闰?很簡(jiǎn)單,說(shuō)明 Client 并不關(guān)心回調(diào)簿透,那么就直接使用 Intent 即可移袍,Provider App 內(nèi)部的回調(diào)行為交給其內(nèi)部處理。


2. PendingIntent 的使用方法

2.1 創(chuàng)建 PendingIntent

PendingIntent 支持在啟動(dòng) Activity老充、Service 或 BroadcastReceiver葡盗。不同類(lèi)型的組件必須使用特定的靜態(tài)方法:

示例程序

// 啟動(dòng) Activity
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
// 啟動(dòng) Service
PendingIntent.Service(Context context, int requestCode, Intent intent, int flags)
// 啟動(dòng) BroadcastReceiver(發(fā)送廣播)
PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)

創(chuàng)建 PendingIntent 后,就可以將 PendingIntent 發(fā)送給其他應(yīng)用啡浊,例如發(fā)送到系統(tǒng)通知消息:

示例程序

// 通知構(gòu)造器
NotificationManagerCompat compat = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    builder = new NotificationCompat.Builder(context, CHANNEL_ID);
} else {
    builder = new NotificationCompat.Builder(context);
}
...
// 設(shè)置 PendingIntent
builder.setContentIntent(pendingIntent);
// 構(gòu)造通知
Notification notification = builder.build()
// 發(fā)送通知
compat.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);

簡(jiǎn)單說(shuō)明下創(chuàng)建 PendingIntent 的 4 個(gè)參數(shù):

  • 1戳粒、context: 當(dāng)前應(yīng)用的上下文路狮,PendingIntent 將從中抽取授權(quán)信息;
  • 2蔚约、requestCode: PendingIntent 的請(qǐng)求碼奄妨,與 Intent 的請(qǐng)求碼類(lèi)似;
  • 3苹祟、intent: 最終的意圖操作砸抛;
  • 4、flag: 控制標(biāo)記位树枫,我們暫且放到一邊直焙。

創(chuàng)建 PendingIntent 時(shí)有一個(gè)容易犯錯(cuò)的地方需要注意:重復(fù)調(diào)用 PendingIntent.getActivity() 等創(chuàng)建方法不一定會(huì)返回新的對(duì)象,系統(tǒng)會(huì)基于兩個(gè)要素判斷是否需要返回相同的 PendingIntent:

  • 要素 1 - requestCode: 不同的 requestCode 會(huì)被認(rèn)為不同的 PendingIntent 意圖砂轻;
  • 要素 2 - Intent: 不同的 Intent 會(huì)被認(rèn)為不同的 PendingIntent 意圖奔誓,但并不是 Intent 中所有的參數(shù)都會(huì)參與計(jì)算,而是僅包含 Intent.filterEquals() 方法考慮的參數(shù)搔涝,即:action厨喂、data、type庄呈、identity蜕煌、class 和 categories,但不包括 extras诬留。

2.2 消費(fèi) PendingIntent

上面提到 PendingIntent 是 Intent 的嵌套類(lèi)斜纪,那么在消費(fèi) PendingIntent 時(shí)是否可以從中取出嵌套的 Intent 再執(zhí)行 startActivity 之類(lèi)的方法呢?NO文兑!消費(fèi) PendingIntent 的方法只能使用 PendingIntent#send() 相關(guān)重載方法盒刚。例如:

PendingIntent.java

public void send() throws CanceledException {
    send(null, 0, null, null, null, null, null);
}

public void send(Context context, int code, @Nullable Intent intent) throws CanceledException {
    send(context, code, intent, null, null, null, null);
}

關(guān)于 send() 內(nèi)部的實(shí)現(xiàn)原理,我們?cè)谙乱还?jié)原理分析中再說(shuō)绿贞。

2.3 取消 PendingIntent

調(diào)用 PendingIntent#cancel() 方法可以取消已經(jīng)創(chuàng)建的 PendingIntent因块,該方法將從系統(tǒng)中移除已經(jīng)注冊(cè)的 PendingIntent(事實(shí)上,是移除 IIntentSender)樟蠕。如果后續(xù)繼續(xù)消費(fèi)這個(gè)已經(jīng)被取消的 PendingIntent贮聂,將拋出 CanceledException 異常靠柑。

PendingIntent.java

private final IIntentSender mTarget;

public void cancel() {
    ActivityManager.getService().cancelIntentSender(mTarget);
}

2.4 可變性與不可變性

PendingIntent 可變性是一種對(duì)外部應(yīng)用消費(fèi)行為的約束機(jī)制寨辩,通過(guò)標(biāo)記位 FLAG_MUTABLEFLAG_IMMUTABLE 控制 PendingIntent 可變或不可變。例如:

示例程序

// 創(chuàng)建可變 PendingIntent
val pendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, intent, PendingIntent.FLAG_MUTABLE)

// 創(chuàng)建不可變 PendingIntent
val pendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)

那么歼冰,可變性意味著什么呢靡狞?可變性意味著在消費(fèi) PendingIntent 時(shí),可以針對(duì)其中包裝的 Intent 進(jìn)行修改隔嫡,即使用 PendingIntent#send(Context, int, Intent) 進(jìn)行修改甸怕。需要注意的是甘穿,這里的 Intent 參數(shù)并不會(huì)完全替換 PendingIntent 中包裝的 Intent,而是將修改的信息填充到原有的 Intent 上梢杭。

源碼摘要

// send() 內(nèi)部通過(guò) Intent#fillIn() 修改 Intent温兼,而不是替換 Intent

// PendingIntent#send() 最終執(zhí)行到:
int changes = finalIntent.fillIn(intent, key.flags);

例如,以下為修改可變 PendingIntent 示例:

示例程序

val intentWithExtrasToFill = Intent().apply {
    putExtra(EXTRA_CUSTOMER_MESSAGE, customerMessage)
}
mutablePendingIntent.send(applicationContext, PENDING_INTENT_CODE, intentWithExtrasToFill)

// 至此武契,PendingIntent 內(nèi)部包裝的 Intent 將持有 EXTRA_CUSTOMER_MESSAGE 信息

另外募判,PendingIntent 可變性的注意事項(xiàng):

  • 注意事項(xiàng) 1 - 修改不可變 PendingIntent: 即使是不可變的 PendingIntent 類(lèi)型,創(chuàng)建 PendingIntent 的應(yīng)用總是可以修改咒唆,因?yàn)榭勺冃灾皇菍?duì)外部應(yīng)用消費(fèi)行為的約束届垫。例如:

修改示例

// 創(chuàng)建不可變 PendingIntent
val pendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)

// 在當(dāng)前應(yīng)用修改不可變 PendingIntent,需要使用 PendingIntent.FLAG_UPDATE_CURRENT 標(biāo)記位
val updatedPendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, anotherIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
  • 注意事項(xiàng) 2 - 顯式指定可變性: FLAG_MUTABLE 可變標(biāo)記位是 Android 12 新增的全释,在 Android 12 之前装处,未使用 FLAG_IMMUTABLE 不可變標(biāo)記位的 PendingIntent 都默認(rèn)是可變的。但是浸船,從 Android 12 開(kāi)始妄迁,為了使 PendingIntent 的處理更加安全,系統(tǒng)要求 PendingIntent 必須顯式聲明一個(gè)可變性標(biāo)志糟袁。這個(gè)問(wèn)題我們?cè)?Android 系統(tǒng)適配手冊(cè) 里講到過(guò)判族。
  • 注意事項(xiàng) 3 - 可變 PendingIntent 需要使用顯式 Intent: 可變 PendingIntent 應(yīng)該將其中包裝的 Intent 設(shè)置為顯式 Intent,確保修改后的 PendingIntent 沒(méi)有安全隱患项戴。

2.5 PendingIntent 標(biāo)記位

現(xiàn)在形帮,我們回過(guò)頭再總結(jié)一下 PendingIntent 的 flags 標(biāo)記位:

  • FLAG_IMMUTABLE: 不可變標(biāo)記位,將約束外部應(yīng)用消費(fèi) PendingIntent 修改其中的 Intent周叮;
  • FLAG_MUTABLE: 可變標(biāo)記位辩撑,不約束外部應(yīng)用消費(fèi) PendingIntent 修改其中的 Intent;
  • FLAG_UPDATE_CURRENT: 更新標(biāo)記位 1仿耽,如果系統(tǒng)中已經(jīng)存在相同的 PendingIntent合冀,那么將保留原有 PendingIntent 對(duì)象,而更新其中的 Intent项贺。即使不可變 PendingIntent君躺,依然可以在當(dāng)前應(yīng)用更新;
  • FLAG_CANCEL_CURRENT: 更新標(biāo)記位 2开缎,如果系統(tǒng)中已經(jīng)存在相同的 PendingIntent棕叫,那么將先取消原有的 PendingIntent,并重新創(chuàng)建新的 PendingIntent奕删。
  • FLAG_NO_CREATE: 更新標(biāo)記位 3俺泣,如果系統(tǒng)中已經(jīng)存在相同的 PendingIntent,那么不會(huì)重新創(chuàng)建,而是直接返回 null伏钠;
  • FLAG_ONE_SHOT: 一次有效標(biāo)記位横漏,PendingIntent 被消費(fèi)后不支持重復(fù)消費(fèi),即只能使用一次熟掂。

3. PendingIntent 實(shí)現(xiàn)原理分析

3.1 創(chuàng)建 PendingIntent 的執(zhí)行過(guò)程

創(chuàng)建 PendingIntent 需要使用特定的靜態(tài)方法缎浇,內(nèi)部會(huì)通過(guò) Binder 通信將 PendingIntent 意圖注冊(cè)到 AMS 系統(tǒng)服務(wù)進(jìn)程中,并獲得一個(gè) Binder 對(duì)象 IIntentSender赴肚。關(guān)鍵源碼摘要如下:

PendingIntent.java

private final IIntentSender mTarget;

// 此處運(yùn)行在應(yīng)用進(jìn)程
public static PendingIntent getActivity(Context context, int requestCode, Intent intent, @Flags int flags) {
    return getActivity(context, requestCode, intent, flags, null);
}

public static PendingIntent getActivity(Context context, int requestCode, @NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
    String packageName = context.getPackageName();
    String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) : null;
    intent.migrateExtraStreamToClipData(context);
    intent.prepareToLeaveProcess(context);
    // 通過(guò) Binder 通信注冊(cè) Intent华畏,得到 IIntentSender
    IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
        ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
        context.getAttributionTag(), null, null, requestCode, new Intent[] { intent },
        resolvedType != null ? new String[] { resolvedType } : null,
        // 注意這個(gè)參數(shù),使用當(dāng)前應(yīng)用的 UserId
        flags, options, context.getUserId());
    return new PendingIntent(target);
}

ActivityManagerService.java

// 此處運(yùn)行在 AMS 系統(tǒng)服務(wù)進(jìn)程
public IIntentSender getIntentSenderWithFeature(int type, String packageName, String featureId,
    IBinder token, String resultWho, int requestCode, Intent[] intents,
    String[] resolvedTypes, int flags, Bundle bOptions, int userId) {
    ...
    int callingUid = Binder.getCallingUid();
    return mPendingIntentController.getIntentSender(type, packageName, featureId,
        callingUid /*調(diào)用應(yīng)用進(jìn)程*/, userId /*原始應(yīng)用進(jìn)程*/, token, resultWho, requestCode, intents, resolvedTypes,
        flags, bOptions);
}

PendingIntentController.java

// 存儲(chǔ)已注冊(cè)的 pendingIntent 記錄
final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords = new HashMap<>();

// 此處運(yùn)行在 AMS 系統(tǒng)服務(wù)進(jìn)程
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) {
        
    // 構(gòu)建 PendingIntent 的 Key
    PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,token, resultWho, requestCode, intents, resolvedTypes, flags, SafeActivityOptions.fromBundle(bOptions), userId);

    WeakReference<PendingIntentRecord> ref = mIntentSenderRecords.get(key);

    // 此處處理以下標(biāo)記位的邏輯
    // FLAG_NO_CREATE
    // FLAG_CANCEL_CURRENT
    // FLAG_UPDATE_CURRENT

    if(ref != null) {
        return ref;
    }
    rec = new PendingIntentRecord(this, key, callingUid);
    mIntentSenderRecords.put(key, rec.ref);
    return rec;
}

PendingIntentRecord.java

public final class PendingIntentRecord extends IIntentSender.Stub {

    final static class Key {

        // 關(guān)鍵參數(shù):創(chuàng)建進(jìn)程的 UserId
        final int userId;

        Key(int _t, String _p, ..., int _userId) {
            ...
            userId = _userId;
        }

        public boolean equals(Object otherObj) {
            ...
            // 要素 1 - requestCode 源碼體現(xiàn)
            if (requestCode != other.requestCode) {
                return false;
            }
            // 要素 2 - Intent 源碼體現(xiàn)
            if (requestIntent != other.requestIntent) {
                if (requestIntent != null) {
                    if (!requestIntent.filterEquals(other.requestIntent)) {
                        return false;
                    }
                } else if (other.requestIntent != null) {
                    return false;
                }
            }
        }
    }   
}

至此尊蚁,PendingIntent 就在系統(tǒng)進(jìn)程中以 PendingIntentRecord 記錄的形式存在亡笑,相當(dāng)于 PendingIntent 是存在于比當(dāng)前應(yīng)用更長(zhǎng)生命周期的系統(tǒng)進(jìn)程中。這就是應(yīng)用進(jìn)程退出后横朋,依然不影響消費(fèi) PendingIntent 的原因仑乌。

3.2 消費(fèi) PendingIntent 執(zhí)行過(guò)程

消費(fèi) PendingIntent 需要使用 PendingIntent#send() 方法,內(nèi)部會(huì)將創(chuàng)建 PendingIntent 時(shí)獲得的 Binder 對(duì)象 IIntentSender 發(fā)送給 AMS 服務(wù)琴锭,用于執(zhí)行最終的 Intent 操作晰甚。關(guān)鍵源碼摘要如下:

PendingIntent.java

private final IIntentSender mTarget;

// 此處運(yùn)行在應(yīng)用進(jìn)程
public void send(Context context, int code, @Nullable Intent intent, ...) throws CanceledException {
    if (sendAndReturnResult(context, code, intent, onFinished, handler, requiredPermission,options) < 0) {
        throw new CanceledException();
    }
}

public int sendAndReturnResult(Context context, int code, @Nullable Intent intent, ...) throws CanceledException {
        // 通過(guò) Binder 通信執(zhí)行 IIntentSender
        return ActivityManager.getService().sendIntentSender(mTarget, mWhitelistToken, code, intent, resolvedType, ...);
}

ActivityManagerService.java

// 此處運(yùn)行在 AMS 系統(tǒng)服務(wù)進(jìn)程
@Override
public int sendIntentSender(IIntentSender target, IBinder whitelistToken, int code, Intent intent, String resolvedType, ...) {
    if (target instanceof PendingIntentRecord) {
        return ((PendingIntentRecord)target).sendWithResult(code, intent, resolvedType, ...);
    }else {
        ...
    }
}

PendingIntentRecord.java

// 此處運(yùn)行在 AMS 系統(tǒng)服務(wù)進(jìn)程
public int sendInner(int code, Intent intent, String resolvedType, ...) {
    // 此處處理以下標(biāo)記位的邏輯
    // FLAG_ONE_SHOT
    // FLAG_MUTABLE
    // FLAG_IMMUTABLE

    // FLAG_ONE_SHOT 標(biāo)記會(huì)移除 PendingIntentController 存儲(chǔ)的記錄
    if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
        controller.cancelIntentSender(this, true);
    }

    int res = START_SUCCESS;

    // 關(guān)鍵參數(shù):創(chuàng)建進(jìn)程的 UserId
    int userId = key.userId;

    switch (key.type) {
        case ActivityManager.INTENT_SENDER_ACTIVITY:
            res = controller.mAtmInternal.startActivitiesInPackage(
                uid /*關(guān)鍵參數(shù)*/, callingPid, callingUid, key.packageName, key.featureId,
                allIntents, allResolvedTypes, resultTo, mergedOptions, userId,
                false /* validateIncomingUser */,
                this /* originatingPendingIntent */,
                mAllowBgActivityStartsForActivitySender.contains(whitelistToken));
        break;

        case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
        ...
        break;

        case ActivityManager.INTENT_SENDER_BROADCAST:
        ...
        break;

        case ActivityManager.INTENT_SENDER_SERVICE:
        case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
        ...
        break;
    }
    return res;
}

ActivityTaskManagerInternal.java

public abstract class ActivityTaskManagerInternal {
    public abstract int startActivityInPackage(int uid, int realCallingPid, int realCallingUid, ...);
}

ActivityTaskManagerInternal 是一個(gè)抽象類(lèi),小彭沒(méi)有找到其最終的實(shí)現(xiàn)類(lèi)决帖,有大佬知道的話(huà)請(qǐng)?jiān)谠u(píng)論區(qū)告訴我厕九。

至此,就完成執(zhí)行 PendingIntent 中延遲操作的目的地回。 那么扁远,為什么在當(dāng)前進(jìn)程執(zhí)行,還會(huì)以另一個(gè)進(jìn)程(PendingIntent 的創(chuàng)建進(jìn)程) 的身份執(zhí)行呢刻像,關(guān)鍵在于使用了保存在 PendingIntentRecord 記錄中的 userId畅买,這與我們通過(guò)常規(guī)的 Activity#startActivityAsUser() 是類(lèi)似的。

Activity.java

@Override
public void startActivityAsUser(Intent intent, UserHandle user) {
    startActivityAsUser(intent, null, user);
}

4. 總結(jié)

到這里细睡,PendingIntent 的內(nèi)容就講完了谷羞,相信你對(duì) PendingIntent 的理解已經(jīng)超過(guò)絕大部分同學(xué),你認(rèn)同嗎溜徙?關(guān)注我湃缎,帶你了解更多,我們下次見(jiàn)蠢壹。


參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嗓违,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子知残,更是在濱河造成了極大的恐慌靠瞎,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件求妹,死亡現(xiàn)場(chǎng)離奇詭異乏盐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)制恍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)父能,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人净神,你說(shuō)我怎么就攤上這事何吝。” “怎么了鹃唯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵爱榕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我坡慌,道長(zhǎng)黔酥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任洪橘,我火速辦了婚禮跪者,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘熄求。我一直安慰自己渣玲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布弟晚。 她就那樣靜靜地躺著忘衍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卿城。 梳的紋絲不亂的頭發(fā)上淑履,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音藻雪,去河邊找鬼秘噪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勉耀,可吹牛的內(nèi)容都是我干的指煎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼便斥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼至壤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起枢纠,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤像街,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體镰绎,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脓斩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畴栖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片随静。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吗讶,靈堂內(nèi)的尸體忽然破棺而出燎猛,到底是詐尸還是另有隱情,我是刑警寧澤照皆,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布重绷,位于F島的核電站,受9級(jí)特大地震影響膜毁,放射性物質(zhì)發(fā)生泄漏论寨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一爽茴、第九天 我趴在偏房一處隱蔽的房頂上張望葬凳。 院中可真熱鬧,春花似錦室奏、人聲如沸火焰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昌简。三九已至,卻和暖如春绒怨,著一層夾襖步出監(jiān)牢的瞬間纯赎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工南蹂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犬金,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓六剥,卻偏偏與公主長(zhǎng)得像晚顷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疗疟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359