請(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_MUTABLE
和 FLAG_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)蠢壹。
參考資料
- PendingIntent —— API 文檔
- IntentSender —— API 文檔
- Intent 和 Intent 過(guò)濾器 —— 官方文檔
- 關(guān)于 PendingIntent 您需要知道的那些事 —— 官方博文
- Android 嵌套 Intent —— 官方博文