活动安全性

Android 可保护用户免受恶意应用的侵害,并提供值得信赖的界面体验。Activity 安全框架包含规则和平台限制。这些规则和限制可防止不必要的界面中断、任务劫持和其他安全威胁。这些威胁与应用组件何时以及如何显示在屏幕上有关。此框架的一个关键组件限制了从后台启动 activity。

后台 activity 启动限制

当不在前台、没有可见 activity 的应用或从其他应用收到的 PendingIntent 尝试启动新 activity 时,就会发生后台 activity 启动 (BAL)。这是后台 activity 启动 (BAL)。 虽然存在正当的使用情形(例如闹钟应用启动时),但不受限制的 BAL 会导致用户体验不佳,并产生安全漏洞。

为什么会受到限制?

自 Android 10(API 级别 29)起,平台对应用何时可以从后台启动 activity 施加了限制。这些保护措施有助于防范恶意应用行为,并通过缓解常见滥用行为(包括以下行为)来改善用户体验:

  • 界面劫持和弹出式广告:某个后台应用在用户当前正在互动的应用之上意外启动一个 activity(通常是广告),从而劫持用户的会话。
  • 钓鱼式攻击和冒充:某个后台应用启动一个冒充其他应用(例如,某个合法应用的虚假登录界面)的 activity,以窃取用户凭据。这通常是通过“Activity Sandwich”攻击实现的,即在合法应用的任务堆栈中插入恶意 activity。
  • 点按劫持:一个后台应用在另一个应用上显示透明或模糊的 activity,以拦截用户的点按操作,并诱骗用户执行意外的操作。
  • 应用唤醒:一个应用的后台组件唤醒另一个应用的前台组件,以非法提升日活跃用户数指标。

前台服务(用于持续性任务)

如果您的应用需要在后台执行用户需要了解的长时间运行的任务(例如播放音乐或跟踪锻炼),则应使用前台服务。前台服务必须显示用户无法关闭的持久性通知。此通知可以提供互动式控件(例如音乐应用的播放/暂停按钮)。这样一来,用户可以随时了解情况并掌控局面,但不会因全屏活动而受到打扰。

遵循此层次结构(从标准通知开始,仅在必要时升级到更具侵入性的选项),您可以为用户打造更好、更可预测的体验。

允许执行后台 activity 的情况(例外情况)

如果满足以下任一条件,应用可以从后台启动 activity:

  • 该应用具有可见窗口,例如在前台运行的 Activity。
  • 应用是当前的输入法 (IME)。
  • activity 是从系统发送的 PendingIntent(例如,从通知点按)启动的。
  • 应用已获得用户授予的 SYSTEM_ALERT_WINDOW 权限。
  • 应用已获得 START_ACTIVITIES_FROM_BACKGROUND 权限。
  • 应用受已获准启动后台 activity 的服务绑定。
  • 启动由设备的启动器应用发起,例如当用户点按应用图标或与 widget 互动时。
  • 启动来自必须始终运行的操作系统核心部分,例如 Telephony 服务启动来电屏幕。

新的安全加固和必需的选择启用功能

为了进一步增强安全性,Android 引入了更严格的规则,要求使用 PendingIntent 和 IntentSender 启动 activity 的应用必须明确选择启用。只有在创建 PendingIntent 的应用或发送 PendingIntent 的应用选择启用以授予其后台启动特权时,才允许启动。

在大多数情况下,发送 PendingIntent 的应用应选择启用,因为该应用通常是用户直接与之互动的应用(例如,点按按钮)。

发送者必须选择启用 PendingIntent

如果您的应用以 Android 14(API 级别 34)或更高版本为目标平台,则在发送 PendingIntent 时,系统不再默认授予其 BAL 权限。如果您未明确选择启用,则 activity 启动可能会被阻止,除非 PendingIntent 的创建者已授予其自己的权限。

为确保启动成功,发送方应选择启用,通过调用 ActivityOptions.setPendingIntentBackgroundActivityStartMode() 来授予其权限,推荐模式为 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE(在 SDK 36 中添加)。

这是一种更严格、更安全的模式。仅当发送应用在发送 PendingIntent 时在屏幕上可见时,才会授予权限。这样可以更有效地确保 activity 启动是用户与应用互动直接引发的结果。

待处理 intent 表
图 1:启动后台 activity 的决策流程。

使用 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 启动 activity 时,也适用相同的 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 限制

待处理 intent 表
图 2:使用 PendingIntent 安全启动 activity 的过程

此图展示了使用 PendingIntent 安全启动 Activity 的过程。成功启动取决于有效的权限链,其中至少有一个参与的应用授予了其权限,并且能够从后台启动 activity

  1. 创建和委托(应用 A - 创建者)
    1. Creator 应用构建 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 系统会启动目标 activity,而发送方会收到成功结果。
    2. BLOCKED:如果两个条件均不满足,系统会阻止启动。 发送方应用不会收到指示失败的直接返回值或异常。相反,Android 系统会在内部向 Logcat 记录“Background activity launch blocked!”消息,开发者必须检查该消息以进行调试。

任务劫持防范

为防止任务内劫持攻击(例如“Activity Sandwich”),Android 15 针对以 API 级别 37 或更高版本为目标平台的应用引入了新规则。

  • 规则 1:在单个任务中,只有属于同一应用(即具有相同 UID)的 activity 才能启动任务中当前最顶层的 activity。
  • 规则 2:只有前台任务中与最上层 activity 的 UID 匹配的 activity 才能创建新任务或将其他现有任务带到前台。

开发者选择启用任务内保护措施

此行为可从目标 SDK 37 开始启用,您必须明确选择启用。它旨在防止任务内劫持(或 Activity Sandwiching),即恶意应用可能会在您的应用的任务中启动 activity,以冒充您的应用并窃取用户数据。

启用保护功能

如需选择启用 ASM,请在 AndroidManifest.xml 文件中将 android:allowCrossUidActivitySwitchFromBelow 属性设置为 false。这是一项应用级设置,默认情况下会保护应用中的所有 activity。

为特定活动创建例外情况

如果您已为应用启用此功能,但需要允许其他应用启动特定的受信任 activity,则可以创建有针对性的例外情况。如需将单个 Activity 从此保护中排除,请在该 Activity 的 onCreate() 方法中调用 setAllowCrossUidActivitySwitchFromBelow(true)。这样,您就可以启动该 activity,同时保持应用的其他部分受到保护。

问题排查

使用正则表达式过滤 Logcat 以查找相关消息。标记 ActivityTaskManager 经常使用,按 ActivityTaskManager 过滤有助于隔离日志。

了解关键日志消息

Blocked Launch (Error):此消息表示 activity 启动被阻止。

分析日志时,请检查以下字段:

  • realCallingPackage:发送 PendingIntent 的应用。这是发件人。
  • callingPackage:创建 PendingIntent 的应用。这是创作者。

严格模式

从 Android 16 开始,应用开发者可以启用严格模式,以便在 activity 启动被阻止时(或在应用的目标 SDK 提高时有被阻止的风险时)收到通知。

以下示例代码展示了如何在 Application、Activity 或其他应用组件的 Application.onCreate() 方法中尽早启用该功能:

override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     StrictMode.setVmPolicy(
         StrictMode.VmPolicy.Builder()
         .detectBlockedBackgroundActivityLaunch()
         .penaltyLog()
         .build());
     )
 }