活動安全性

Android 可保護使用者免受惡意應用程式侵擾,並提供值得信賴的 UI 體驗。活動安全架構包含規則和平台限制。這些規則和限制可防止不必要的 UI 中斷、工作遭劫持和其他安全威脅。這些威脅與應用程式元件在畫面上顯示的時間和方式有關。這個架構的重要元件會限制從背景啟動活動。

啟動背景活動的限制

當應用程式不在前景、沒有可見活動,或收到來自其他應用程式的 PendingIntent,並嘗試啟動新活動時,就會發生背景活動啟動 (BAL) 情況。這是背景活動啟動 (BAL)。 雖然有正當用途 (例如啟動鬧鐘應用程式),但無限制的 BAL 會導致使用者體驗不佳,並造成安全漏洞。

為何受到限制?

自 Android 10 (API 級別 29) 起,平台對應用程式從背景啟動活動的時間設下限制。這些防護措施有助於防範惡意應用程式行為,並減輕常見濫用行為 (包括):

  • UI 劫持和彈出式廣告:背景應用程式在使用者目前互動的應用程式上方,意外啟動活動 (通常是廣告),劫持使用者的工作階段。
  • 網路釣魚和冒用身分:背景應用程式啟動的活動會冒用其他應用程式 (例如合法應用程式的偽造登入畫面),藉此竊取使用者憑證。這通常是透過「活動三明治」攻擊達成,也就是將惡意活動插入合法應用程式的任務堆疊。
  • 輕觸劫持:背景應用程式在另一個應用程式上顯示透明或遮蔽的活動,攔截使用者的輕觸動作,誘騙他們採取非預期的動作。
  • 應用程式喚醒:某個應用程式的背景元件喚醒另一個應用程式的前景元件,藉此非法提高每日活躍使用者指標。

前景服務 (適用於持續性工作)

如果應用程式需要在背景執行長時間工作,且使用者需要瞭解這項工作,例如播放音樂或追蹤運動,您應該使用前景服務。前景服務必須顯示無法由使用者關閉的持續性通知。這類通知可提供互動式控制項 (例如音樂應用程式的播放/暫停按鈕)。這樣一來,使用者就能掌握情況並保有控制權,但不會因為全螢幕活動而受到干擾。

請按照這個優先順序,從標準通知開始,只在必要時改用更具侵入性的選項,為使用者打造更優質、更可預測的體驗。

允許啟動背景活動 (例外狀況)

如果符合下列任一條件,應用程式就能從背景啟動活動:

  • 應用程式有可見視窗,例如前景活動。
  • 應用程式是目前的輸入法編輯器 (IME)。
  • 活動是從系統傳送的 PendingIntent 啟動 (例如輕觸通知)。
  • 應用程式已取得使用者授予的 SYSTEM_ALERT_WINDOW 權限。
  • 應用程式已獲得 START_ACTIVITIES_FROM_BACKGROUND 權限。
  • 應用程式繫結至已獲准啟動背景活動的服務。
  • 啟動作業是由裝置的啟動器應用程式發起,例如使用者輕觸應用程式圖示或與小工具互動時。
  • 啟動作業來自作業系統的核心部分,必須隨時執行,例如電話服務啟動來電畫面。

強化安全防護並強制啟用

為進一步提升安全性,Android 推出更嚴格的規則,要求使用 PendingIntentIntentSender 啟動活動的應用程式,必須明確徵求使用者同意。只有在建立 PendingIntent 的應用程式或傳送該意圖的應用程式選擇加入授予背景啟動權限時,系統才會允許啟動。

在大多數情況下,傳送 PendingIntent 的應用程式應選擇加入,因為使用者通常是直接與該應用程式互動 (例如輕觸按鈕)。

傳送者必須選擇採用 PendingIntent

如果應用程式指定 Android 14 (API 級別 34) 以上版本,傳送 PendingIntent 時,系統不會再預設授予 BAL 權限。如未明確選擇加入,活動啟動可能會遭到封鎖,除非 PendingIntent 的建立者已授予自己的權限。

為確保啟動成功,傳送者應呼叫 ActivityOptions.setPendingIntentBackgroundActivityStartMode(),並將建議模式設為 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (SDK 36 中新增),選擇加入以授予權限。

這是更嚴格且更安全的模式。只有在傳送 PendingIntent 時,傳送應用程式顯示在螢幕上,系統才會授予權限。這樣可更強烈地確保活動啟動是使用者與應用程式互動的直接結果。

待處理意圖表格
圖 1:啟動背景活動的決策流程。

使用 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())
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())

序列圖:BAL 限制

待處理意圖表格
圖 2:使用 PendingIntent 安全啟動活動的程序

這張圖表說明如何使用 PendingIntent 安全地啟動活動。如要順利啟動,必須有有效的權限鏈結,其中至少一個參與的應用程式會授予權限,並能從背景啟動活動

  1. 建立及委派 (應用程式 A - 創作者)
    1. 建立器應用程式會建構 PendingIntent
    2. 如果目標 SDK 為 35 以上,建立者必須使用 setPendingIntentCreatorBackgroundActivityStartMode() 明確委派 BAL 權限,才能使用這些權限。根據預設,系統不會委派任何權限。
    3. 接著,系統會將 PendingIntent 傳送至其他應用程式 (應用程式 B)
  2. 啟動與貢獻 (應用程式 B - 傳送者)
    1. 稍後,傳送端應用程式會呼叫 PendingIntent.send() 來啟動啟動程序。
    2. 如果目標 SDK 為 34 以上,且寄件者希望使用自己的權限,則必須使用 setPendingIntentBackgroundActivityStartMode() 明確提供權限。根據預設,系統不會委派任何權限。
  3. Android 系統安全性驗證
    1. Android 系統會攔截啟動要求,並執行安全性檢查。
    2. 這項函式會評估兩項條件:
    3. 創作者是否已委派權限,且創作者應用程式目前是否符合一般 BAL 例外狀況?
    4. 寄件者是否貢獻了權限,且寄件者應用程式目前是否符合一般 BAL 例外狀況?
  4. 結果
    1. 允許:如果至少一個步驟 3 中的條件符合,權限鏈結就會通過驗證。Android 系統會啟動目標活動,傳送者則會收到成功結果。
    2. 已封鎖:如果兩項條件都不符合,系統會封鎖啟動。 傳送端應用程式不會收到直接傳回值或例外狀況,指出失敗。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 篩選有助於找出記錄。

瞭解重要記錄訊息

已封鎖啟動 (錯誤):這則訊息表示活動啟動程序遭到封鎖。

分析記錄時,請檢查下列欄位:

  • 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());
     )
 }