[AMS] Android 后台进程启动 activity 限制

本文详细描述了Android10及以上版本对应用在后台启动activity的规则,涉及权限、可见窗口、系统服务绑定、PendingIntent行为以及特定场景下的启动许可。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://developer.android.google.cn/guide/components/activities/background-starts?hl=lt

Android 10(API 级别 29)及更高版本对应用在后台运行时可以启动 activity 的时间施加了限制。这些限制有助于最大限度地减少对用户造成的干扰,并且可以让用户更好地控制其屏幕上显示的内容。

frameworks/base/services/core/java/com/android/server/wm/BackgroundActivityStartController.java

 boolean shouldAbortBackgroundActivityStart(
            int callingUid,
            int callingPid,
            final String callingPackage,
            int realCallingUid,
            int realCallingPid,
            WindowProcessController callerApp,
            PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart,
            Intent intent,
            ActivityOptions checkedOptions) {
        return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
                realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
                allowBackgroundActivityStart, intent, checkedOptions) == BAL_BLOCK;
    }

    /**
     * @return A code denoting which BAL rule allows an activity to be started,
     * or {@link BAL_BLOCK} if the launch should be blocked
     */
    @BalCode
    int checkBackgroundActivityStart(
            int callingUid,
            int callingPid,
            final String callingPackage,
            int realCallingUid,
            int realCallingPid,
            WindowProcessController callerApp,
            PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart,
            Intent intent,
            ActivityOptions checkedOptions) {
        // don't abort for the most important UIDs
        final int callingAppId = UserHandle.getAppId(callingUid);
        final boolean useCallingUidState =
                originatingPendingIntent == null
                        || checkedOptions == null
                        || !checkedOptions.getIgnorePendingIntentCreatorForegroundState();
        if (useCallingUidState) {
            if (callingUid == Process.ROOT_UID
                    || callingAppId == Process.SYSTEM_UID
                    || callingAppId == Process.NFC_UID) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
                        callingUid, realCallingUid, intent, "Important callingUid");
            }

            // Always allow home application to start activities.
            if (isHomeApp(callingUid, callingPackage)) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "Home app");
            }

            // IME should always be allowed to start activity, like IME settings.
            final WindowState imeWindow =
                    mService.mRootWindowContainer.getCurrentInputMethodWindow();
            if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "Active ime");
            }
        }

        // This is used to block background activity launch even if the app is still
        // visible to user after user clicking home button.
        final int appSwitchState = mService.getBalAppSwitchesState();

        // don't abort if the callingUid has a visible window or is a persistent system process
        final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
        final boolean callingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
        final boolean isCallingUidForeground =
                callingUidHasAnyVisibleWindow
                        || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
                        || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
        final boolean isCallingUidPersistentSystemProcess =
                callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;

        // Normal apps with visible app window will be allowed to start activity if app switching
        // is allowed, or apps like live wallpaper with non app visible window will be allowed.
        final boolean appSwitchAllowedOrFg =
                appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
        final boolean allowCallingUidStartActivity =
                ((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
                                && callingUidHasAnyVisibleWindow)
                        || isCallingUidPersistentSystemProcess;
        if (useCallingUidState && allowCallingUidStartActivity) {
            return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW,
                    /*background*/ false, callingUid, realCallingUid, intent,
                    "callingUidHasAnyVisibleWindow = "
                            + callingUid
                            + ", isCallingUidPersistentSystemProcess = "
                            + isCallingUidPersistentSystemProcess);
        }
        // take realCallingUid into consideration
        final int realCallingUidProcState =
                (callingUid == realCallingUid)
                        ? callingUidProcState
                        : mService.mActiveUids.getUidState(realCallingUid);
        final boolean realCallingUidHasAnyVisibleWindow =
                (callingUid == realCallingUid)
                        ? callingUidHasAnyVisibleWindow
                        : mService.hasActiveVisibleWindow(realCallingUid);
        final boolean isRealCallingUidForeground =
                (callingUid == realCallingUid)
                        ? isCallingUidForeground
                        : realCallingUidHasAnyVisibleWindow
                                || realCallingUidProcState == ActivityManager.PROCESS_STATE_TOP;
        final int realCallingAppId = UserHandle.getAppId(realCallingUid);
        final boolean isRealCallingUidPersistentSystemProcess =
                (callingUid == realCallingUid)
                        ? isCallingUidPersistentSystemProcess
                        : (realCallingAppId == Process.SYSTEM_UID)
                                || realCallingUidProcState
                                        <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;

        // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
        // visible window.
        if (Process.isSdkSandboxUid(realCallingUid)) {
            int realCallingSdkSandboxUidToAppUid =
                    Process.getAppUidForSdkSandboxUid(UserHandle.getAppId(realCallingUid));

            if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "uid in SDK sandbox has visible (non-toast) window");
            }
        }

        // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
        final boolean balAllowedByPiSender =
                PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions);

        if (balAllowedByPiSender && realCallingUid != callingUid) {
            final boolean useCallerPermission =
                    PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions);
            if (useCallerPermission
                    && ActivityManager.checkComponentPermission(
                                    android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                                    realCallingUid,
                                    -1,
                                    true)
                            == PackageManager.PERMISSION_GRANTED) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "realCallingUid has BAL permission. realCallingUid: " + realCallingUid);
            }

            // don't abort if the realCallingUid has a visible window
            // TODO(b/171459802): We should check appSwitchAllowed also
            if (realCallingUidHasAnyVisibleWindow) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "realCallingUid has visible (non-toast) window. realCallingUid: "
                                + realCallingUid);
            }
            // if the realCallingUid is a persistent system process, abort if the IntentSender
            // wasn't allowed to start an activity
            if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "realCallingUid is persistent system process AND intent "
                                + "sender allowed (allowBackgroundActivityStart = true). "
                                + "realCallingUid: " + realCallingUid);
            }
            // don't abort if the realCallingUid is an associated companion app
            if (mService.isAssociatedCompanionApp(
                    UserHandle.getUserId(realCallingUid), realCallingUid)) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
                        /*background*/ false, callingUid, realCallingUid, intent,
                        "realCallingUid is a companion app. "
                                + "realCallingUid: " + realCallingUid);
            }
        }
        if (useCallingUidState) {
            // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
            if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
                    callingPid, callingUid) == PERMISSION_GRANTED) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_BAL_PERMISSION,
                        /*background*/ true, callingUid, realCallingUid, intent,
                        "START_ACTIVITIES_FROM_BACKGROUND permission granted");
            }
            // don't abort if the caller has the same uid as the recents component
            if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                        /*background*/ true, callingUid, realCallingUid,
                        intent, "Recents Component");
            }
            // don't abort if the callingUid is the device owner
            if (mService.isDeviceOwner(callingUid)) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                        /*background*/ true, callingUid, realCallingUid,
                        intent, "Device Owner");
            }
            // don't abort if the callingUid has companion device
            final int callingUserId = UserHandle.getUserId(callingUid);
            if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                        /*background*/ true, callingUid, realCallingUid,
                        intent, "Companion App");
            }
            // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
            if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
                Slog.w(
                        TAG,
                        "Background activity start for "
                                + callingPackage
                                + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
                return logStartAllowedAndReturnCode(BAL_ALLOW_SAW_PERMISSION,
                        /*background*/ true, callingUid, realCallingUid,
                        intent, "SYSTEM_ALERT_WINDOW permission is granted");
            }
        }
        // If we don't have callerApp at this point, no caller was provided to startActivity().
        // That's the case for PendingIntent-based starts, since the creator's process might not be
        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
        // caller if caller allows, so that we can make the decision based on its state.
        int callerAppUid = callingUid;
        if (callerApp == null && balAllowedByPiSender) {
            callerApp = mService.getProcessController(realCallingPid, realCallingUid);
            callerAppUid = realCallingUid;
        }
        // don't abort if the callerApp or other processes of that uid are allowed in any way
        if (callerApp != null && useCallingUidState) {
            // first check the original calling process
            @BalCode int balAllowedForCaller = callerApp
                    .areBackgroundActivityStartsAllowed(appSwitchState);
            if (balAllowedForCaller != BAL_BLOCK) {
                return logStartAllowedAndReturnCode(balAllowedForCaller,
                        /*background*/ true, callingUid, realCallingUid, intent,
                        "callerApp process (pid = " + callerApp.getPid()
                                + ", uid = " + callerAppUid + ") is allowed");
            }
            // only if that one wasn't allowed, check the other ones
            final ArraySet<WindowProcessController> uidProcesses =
                    mService.mProcessMap.getProcesses(callerAppUid);
            if (uidProcesses != null) {
                for (int i = uidProcesses.size() - 1; i >= 0; i--) {
                    final WindowProcessController proc = uidProcesses.valueAt(i);
                    int balAllowedForUid = proc.areBackgroundActivityStartsAllowed(appSwitchState);
                    if (proc != callerApp
                            && balAllowedForUid != BAL_BLOCK) {
                        return logStartAllowedAndReturnCode(balAllowedForUid,
                                /*background*/ true, callingUid, realCallingUid, intent,
                                "process" + proc.getPid()
                                        + " from uid " + callerAppUid + " is allowed");
                    }
                }
            }
        }
        // anything that has fallen through would currently be aborted
        Slog.w(
                TAG,
                "Background activity start [callingPackage: "
                        + callingPackage
                        + "; callingUid: "
                        + callingUid
                        + "; appSwitchState: "
                        + appSwitchState
                        + "; isCallingUidForeground: "
                        + isCallingUidForeground
                        + "; callingUidHasAnyVisibleWindow: "
                        + callingUidHasAnyVisibleWindow
                        + "; callingUidProcState: "
                        + DebugUtils.valueToString(
                                ActivityManager.class, "PROCESS_STATE_", callingUidProcState)
                        + "; isCallingUidPersistentSystemProcess: "
                        + isCallingUidPersistentSystemProcess
                        + "; realCallingUid: "
                        + realCallingUid
                        + "; isRealCallingUidForeground: "
                        + isRealCallingUidForeground
                        + "; realCallingUidHasAnyVisibleWindow: "
                        + realCallingUidHasAnyVisibleWindow
                        + "; realCallingUidProcState: "
                        + DebugUtils.valueToString(
                                ActivityManager.class, "PROCESS_STATE_", realCallingUidProcState)
                        + "; isRealCallingUidPersistentSystemProcess: "
                        + isRealCallingUidPersistentSystemProcess
                        + "; originatingPendingIntent: "
                        + originatingPendingIntent
                        + "; allowBackgroundActivityStart: "
                        + allowBackgroundActivityStart
                        + "; intent: "
                        + intent
                        + "; callerApp: "
                        + callerApp
                        + "; inVisibleTask: "
                        + (callerApp != null && callerApp.hasActivityInVisibleTask())
                        + "]");
        // log aborted activity start to TRON
        if (mService.isActivityStartsLoggingEnabled()) {
            mSupervisor
                    .getActivityMetricsLogger()
                    .logAbortedBgActivityStart(
                            intent,
                            callerApp,
                            callingUid,
                            callingPackage,
                            callingUidProcState,
                            callingUidHasAnyVisibleWindow,
                            realCallingUid,
                            realCallingUidProcState,
                            realCallingUidHasAnyVisibleWindow,
                            (originatingPendingIntent != null));
        }
        return BAL_BLOCK;
    }

根据代码,在满足以下一个或多个条件时,在 Android 10 或更高版本上运行的应用可以启动 activity:

  • 应用具有可见窗口,例如前台 activity。
  • 该应用在前台任务的返回堆栈中有 activity。
  • 应用在“最近使用的应用”屏幕上现有任务的返回堆栈中有一个 activity。

    注意 :当此类应用尝试启动新的 activity 时,系统会将该 activity 置于应用的现有任务之上,但不会离开当前可见的任务。当用户稍后返回应用任务时,系统会启动新的 activity,而不是之前在应用任务之上的 activity。

  • 应用有一个最近启动的 activity。

  • 应用最近对一个 activity 调用了 finish()。这仅适用于在调用 finish() 时,应用在前台运行的 activity,或在前台任务的返回堆栈中具有 activity。

  • 该应用具有以下某个受系统绑定的服务。 这些服务可能需要启动一个界面。

  • 该应用的一项服务被另一个可见的应用绑定。绑定到该服务的应用必须保持可见,以便后台应用成功启动 activity。

    注意 :从 Android 14 开始,如果绑定到该服务的应用以 Android 14 或更高版本为目标平台,它将不再允许具有该服务的应用默认启动后台 activity。应用必须传递 Context.BIND_ALLOW_ACTIVITY_STARTS 标志,以允许绑定服务应用启动后台 activity。

  • 应用会从系统收到通知 PendingIntent。如果是服务和广播接收器的待处理 intent,则应用可以在待处理 intent 发送后启动 activity 几秒钟。

  • 应用会收到来自其他可见应用发送的 PendingIntent

    注意 :从 Android 14 开始,以 Android 14 或更高版本为目标平台的应用在发送 PendingIntent 时必须选择允许启动后台 activity。若要选择接受,该应用应传递一个ActivityOptions捆绑包setPendingIntentBackgroundActivityStartMode (ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)

  • 应用收到一条系统广播,在该广播中,应用应启动界面。例如 ACTION_NEW_OUTGOING_CALL 和 SECRET_CODE_ACTION。应用可在广播发送几秒钟后启动 Activity。

  • 应用通过 CompanionDeviceManager API 与配套硬件设备相关联。此 API 可让应用启动 activity 以响应用户在配对设备上执行的操作。

  • 应用是在设备所有者模式下运行的设备政策控制器。示例使用场景包括全托管式企业设备以及数字标牌和自助服务终端等专用设备

  • 应用被用户授予 SYSTEM_ALERT_WINDOW 权限。

注意 :在 Android 10(Go 版本)上运行的应用无法获得 SYSTEM_ALERT_WINDOW 权限

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值