Android 可保護使用者免受惡意應用程式侵擾,並提供值得信賴的 UI 體驗。活動安全架構包含規則和平台限制。這些規則和限制可防止不必要的 UI 中斷、工作遭劫持和其他安全威脅。這些威脅與應用程式元件在畫面上顯示的時間和方式有關。這個架構的重要元件會限制從背景啟動活動。
啟動背景活動的限制
當應用程式不在前景、沒有可見活動,或收到來自其他應用程式的 PendingIntent,並嘗試啟動新活動時,就會發生背景活動啟動 (BAL) 情況。這是背景活動啟動 (BAL)。
雖然有正當用途 (例如啟動鬧鐘應用程式),但無限制的 BAL 會導致使用者體驗不佳,並造成安全漏洞。
為何受到限制?
自 Android 10 (API 級別 29) 起,平台對應用程式從背景啟動活動的時間設下限制。這些防護措施有助於防範惡意應用程式行為,並減輕常見濫用行為 (包括):
- UI 劫持和彈出式廣告:背景應用程式在使用者目前互動的應用程式上方,意外啟動活動 (通常是廣告),劫持使用者的工作階段。
- 網路釣魚和冒用身分:背景應用程式啟動的活動會冒用其他應用程式 (例如合法應用程式的偽造登入畫面),藉此竊取使用者憑證。這通常是透過「活動三明治」攻擊達成,也就是將惡意活動插入合法應用程式的任務堆疊。
- 輕觸劫持:背景應用程式在另一個應用程式上顯示透明或遮蔽的活動,攔截使用者的輕觸動作,誘騙他們採取非預期的動作。
- 應用程式喚醒:某個應用程式的背景元件喚醒另一個應用程式的前景元件,藉此非法提高每日活躍使用者指標。
前景服務 (適用於持續性工作)
如果應用程式需要在背景執行長時間工作,且使用者需要瞭解這項工作,例如播放音樂或追蹤運動,您應該使用前景服務。前景服務必須顯示無法由使用者關閉的持續性通知。這類通知可提供互動式控制項 (例如音樂應用程式的播放/暫停按鈕)。這樣一來,使用者就能掌握情況並保有控制權,但不會因為全螢幕活動而受到干擾。
請按照這個優先順序,從標準通知開始,只在必要時改用更具侵入性的選項,為使用者打造更優質、更可預測的體驗。
允許啟動背景活動 (例外狀況)
如果符合下列任一條件,應用程式就能從背景啟動活動:
- 應用程式有可見視窗,例如前景活動。
- 應用程式是目前的輸入法編輯器 (IME)。
- 活動是從系統傳送的
PendingIntent啟動 (例如輕觸通知)。 - 應用程式已取得使用者授予的
SYSTEM_ALERT_WINDOW權限。 - 應用程式已獲得
START_ACTIVITIES_FROM_BACKGROUND權限。 - 應用程式繫結至已獲准啟動背景活動的服務。
- 啟動作業是由裝置的啟動器應用程式發起,例如使用者輕觸應用程式圖示或與小工具互動時。
- 啟動作業來自作業系統的核心部分,必須隨時執行,例如電話服務啟動來電畫面。
強化安全防護並強制啟用
為進一步提升安全性,Android 推出更嚴格的規則,要求使用 PendingIntent 和 IntentSender 啟動活動的應用程式,必須明確徵求使用者同意。只有在建立 PendingIntent 的應用程式或傳送該意圖的應用程式選擇加入授予背景啟動權限時,系統才會允許啟動。
在大多數情況下,傳送 PendingIntent 的應用程式應選擇加入,因為使用者通常是直接與該應用程式互動 (例如輕觸按鈕)。
傳送者必須選擇採用 PendingIntent
如果應用程式指定 Android 14 (API 級別 34) 以上版本,傳送 PendingIntent 時,系統不會再預設授予 BAL 權限。如未明確選擇加入,活動啟動可能會遭到封鎖,除非 PendingIntent 的建立者已授予自己的權限。
為確保啟動成功,傳送者應呼叫 ActivityOptions.setPendingIntentBackgroundActivityStartMode(),並將建議模式設為 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (SDK 36 中新增),選擇加入以授予權限。
這是更嚴格且更安全的模式。只有在傳送 PendingIntent 時,傳送應用程式顯示在螢幕上,系統才會授予權限。這樣可更強烈地確保活動啟動是使用者與應用程式互動的直接結果。
使用 ActivityOptions.setPendingIntentBackgroundActivityStartMode() 授予權限。
// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
try {
myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}
try {
myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "The PendingIntent was canceled", e)
}
創作者必須選擇使用 PendingIntent
如果應用程式指定 Android 15 (API 級別 35) 以上版本,建立 PendingIntent 的應用程式預設不會授予背景啟動權限。如要允許寄件者使用應用程式的 BAL 權限,您必須明確選擇加入。
使用 ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() 授予權限。
// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
PendingIntent.FLAG_IMMUTABLE, options.toBundle())
使用 IntentSender 啟動
使用 IntentSender 啟動活動時,也適用相同的 BAL 限制。由於 IntentSender 是透過 PendingIntent.getIntentSender 取得,因此須遵守類似的同意聲明規定。
- 自 Android 14 (API 34) 起,使用 Context.startIntentSender() 時,必須在傳送端選擇加入。您也必須在此提供
ActivityOptions套件。
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle())
- 自 Android 17 (API 37 以上版本) 起,使用 IntentSender.sendIntent() 時,必須在傳送端選擇加入。
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle())
- 使用 ActivityResultLauncher
: 這個 AndroidX API 會在內部使用 Context.startIntentSender(),因此會受到 BAL 限制影響。
序列圖:BAL 限制
這張圖表說明如何使用 PendingIntent 安全地啟動活動。如要順利啟動,必須有有效的權限鏈結,其中至少一個參與的應用程式會授予權限,並能從背景啟動活動
- 建立及委派 (應用程式 A - 創作者)
- 建立器應用程式會建構
PendingIntent - 如果目標 SDK 為 35 以上,建立者必須使用 setPendingIntentCreatorBackgroundActivityStartMode() 明確委派 BAL 權限,才能使用這些權限。根據預設,系統不會委派任何權限。
- 接著,系統會將
PendingIntent傳送至其他應用程式 (應用程式 B)
- 建立器應用程式會建構
- 啟動與貢獻 (應用程式 B - 傳送者)
- 稍後,傳送端應用程式會呼叫
PendingIntent.send()來啟動啟動程序。 - 如果目標 SDK 為 34 以上,且寄件者希望使用自己的權限,則必須使用 setPendingIntentBackgroundActivityStartMode() 明確提供權限。根據預設,系統不會委派任何權限。
- 稍後,傳送端應用程式會呼叫
- Android 系統安全性驗證
- Android 系統會攔截啟動要求,並執行安全性檢查。
- 這項函式會評估兩項條件:
- 創作者是否已委派權限,且創作者應用程式目前是否符合一般 BAL 例外狀況?
- 寄件者是否貢獻了權限,且寄件者應用程式目前是否符合一般 BAL 例外狀況?
- 結果
- 允許:如果至少一個步驟 3 中的條件符合,權限鏈結就會通過驗證。Android 系統會啟動目標活動,傳送者則會收到成功結果。
- 已封鎖:如果兩項條件都不符合,系統會封鎖啟動。 傳送端應用程式不會收到直接傳回值或例外狀況,指出失敗。Android 系統會在內部將「Background activity launch blocked!」訊息記錄到 Logcat,開發人員必須檢查這則訊息才能進行偵錯。
防止工作遭駭
為防止工作內劫持攻擊 (例如「Activity Sandwich」),Android 15 針對指定 API 級別 37 以上版本的應用程式,導入了新規則。
- 規則 1:在單一工作中,活動只能由屬於同一應用程式 (也就是具有相同 UID) 的另一個活動啟動,且該活動是工作中的目前頂端活動。
- 規則 2:只有前景工作中的活動 (須與最上層活動的 UID 相符),才能建立新工作或將其他現有工作帶到前景。
開發人員選擇啟用工作內保護措施
您可以從目標 SDK 37 開始啟用這項行為,但必須明確選擇啟用。這項功能旨在防止工作內劫持 (或活動夾層),避免惡意應用程式在您應用程式的工作中啟動活動,藉此冒用身分並竊取使用者資料。
啟用保護措施
如要選擇採用並為應用程式啟用 ASM,請在 AndroidManifest.xml 檔案中將 android:allowCrossUidActivitySwitchFromBelow 屬性設為 false。這是應用程式層級的設定,預設會保護應用程式中的所有活動。
為特定活動建立例外狀況
如果您已為應用程式啟用這項功能,但需要允許其他應用程式啟動特定信任的活動,可以建立目標例外狀況。如要讓單一活動免受這項保護措施影響,請在該活動的 onCreate() 方法中呼叫 setAllowCrossUidActivitySwitchFromBelow(true)。這樣一來,您就能啟動該活動,同時保護應用程式的其餘部分。
疑難排解
使用規則運算式篩選 Logcat,找出相關訊息。通常會使用 ActivityTaskManager 標記,而依 ActivityTaskManager 篩選有助於找出記錄。
瞭解重要記錄訊息
已封鎖啟動 (錯誤):這則訊息表示活動啟動程序遭到封鎖。
- 意義:系統拒絕啟動活動,因為傳送者 (適用於 SDK 34 以上版本) 或建立者 (適用於 SDK 35 以上版本) 缺少必要的 PendingIntent 選擇加入設定。
- 行動:您必須更新程式碼,加入正確的 ActivityOptions 參與意願選項。
分析記錄時,請檢查下列欄位:
- realCallingPackage:傳送 PendingIntent 的應用程式。這是傳送者。
- callingPackage:建立 PendingIntent 的應用程式。這是創作者。
嚴格模式
從 Android 16 開始,應用程式開發人員可以啟用嚴格模式,在活動啟動遭封鎖時收到通知 (或在應用程式的目標 SDK 提高時,收到活動啟動可能遭封鎖的通知)。
以下是範例程式碼,可從 Application、Activity 或其他應用程式元件的 Application.onCreate() 方法中啟用:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectBlockedBackgroundActivityLaunch()
.penaltyLog()
.build());
)
}