如何解决Android 后台进程启动 activity 限制?

背景:

在上一篇文章:
(有源码)重学Input实战项目:手把手带你实现按键一键启动Activity

在文章最后有给大家布置个小思考小作业,那就是直接后台广播来启动Activity会有如下报错:
Background activity launch blocked!
那么这个问题应该如何解决呢?需要解决这个后台Activity启动问题就需要查阅报错相关的源码和google的官方文档。
同时vip群的学员们也纷纷进行讨论提出自己观点:

在这里插入图片描述

官方文档:

关于后台启动Activity相关详细文档可以看这里:
https://developer.android.google.cn/guide/components/activities/background-starts

文档中的核心部分就是指出了哪些情况app是可以后台启动的,下面也有详细的列出哪些情况:

When apps can start activities

Apps running on Android 10 or higher can start activities when one or more of the following conditions are met:

    Note: Starting from Android 14, an explicit opt-in is required in addition to meeting these conditions.

    The app has a visible window, such as an activity in the foreground.
    The app has an activity in the back stack of the foreground task.

    The app has an activity in the back stack of an existing task on the Recents screen.
    Note: When such an app attempts to start a new activity, the system places that activity on top of the app's existing task but doesn't navigate away from the currently visible task. When the user later returns to the app's task, the system starts the new activity instead of the activity that had previously been on top of the app's task.

    The app has an activity that started very recently.

    The app called finish() on an activity very recently. This applies only when the app had either an activity in the foreground or an activity in the back stack of the foreground task at the time finish() was called.

    The app has one of the following services that is bound by the system. These services might need to launch a UI.
        AccessibilityService
        AutofillService
        CallRedirectionService
        HostApduService
        InCallService
        TileService (Not applicable in Android 14 (API level 34) and higher)
        VoiceInteractionService
        VrListenerService.

    The app has a service that is bound by a different, visible app. The app bound to the service must remain visible for the app in the background to start activities successfully.

        Note: Starting from Android 14, if the app bound to the service is targeting Android 14 or higher, it no longer allows the app that has the service to start a background activity by default. The app has to pass the flag Context.BIND_ALLOW_ACTIVITY_STARTS to allow the bound service app to start background activities.

    The app receives a notification PendingIntent from the system. In the case of pending intents for services and broadcast receivers, the app can start activities for a few seconds after the pending intent is sent.

    The app receives a PendingIntent that is sent from a different, visible app.

    The app receives a system broadcast where the app is expected to launch a UI. Examples include ACTION_NEW_OUTGOING_CALL and SECRET_CODE_ACTION. The app can start activities for a few seconds after the broadcast is sent.

    The app is associated with a companion hardware device through the CompanionDeviceManager API. This API lets the app start activities in response to actions that the user performs on a paired device.

    The app is a device policy controller running in device owner mode. Example use cases include fully managed enterprise devices as well as dedicated devices like digital signage and kiosks.

    The app is granted the SYSTEM_ALERT_WINDOW permission by the user.
    Note: Apps running on Android 10 (Go edition) cannot receive the SYSTEM_ALERT_WINDOW permission.


这里翻译成中文:

1、应用具有可见窗口,例如在前台运行的 Activity。
2、应用在前台任务的返回栈中具有一项 activity。
3、应用在“最近使用的应用”屏幕上现有任务的返回堆栈中具有 activity。
4、该应用具有最近启动的 activity。
5、应用对最近的一项 activity 调用了 finish()。这仅适用于在调用 finish() 时,应用在前台中具有一项 activity,或在前台任务的返回栈中具有一项 activity 的情况。

5、应用有以下列表的服务被系统绑定。这些服务可能需要启动界面。

AccessibilityService
AutofillService
CallRedirectionService
HostApduService
InCallService
TileService(不适用于 Android 14 [API 级别 34] 及更高版本)
VoiceInteractionService
VrListenerService。

6、应用的某项服务被其他可见应用绑定。绑定到该服务的应用必须在后台对该应用保持可见,才能成功启动 activity。

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

7、应用会从系统收到通知 PendingIntent。如果存在针对服务和广播接收器的待定 intent,则该应用可以在待定 intent 发送后启动 activity 几秒钟时间。

8、应用会收到从其他可见应用发送的 PendingIntent。

9、应用接收到系统广播,并且应用应启动界面。例如 ACTION_NEW_OUTGOING_CALL 和 SECRET_CODE_ACTION。 应用可在广播发送几秒钟后启动 Activity。

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

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

12、该应用已获得用户授予的 SYSTEM_ALERT_WINDOW 权限。

对应代码

下面代码就是对google文档的最好验证

 /**
     * @return A code denoting which BAL rule allows an activity to be started,
     * or {@link #BAL_BLOCK} if the launch should be blocked
     */
    BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
        // This is used to block background activity launch even if the app is still
        // visible to user after user clicking home button.

        // 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 = state.mAppSwitchState == APP_SWITCH_ALLOW
                || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
        if (appSwitchAllowedOrFg && state.mCallingUidHasAnyVisibleWindow) {
            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                    /*background*/ false, "callingUid has visible window");
        }
        if (mService.mActiveUids.hasNonAppVisibleWindow(state.mCallingUid)) {
            return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
                    /*background*/ false, "callingUid has non-app visible window");
        }

        // don't abort for the most important UIDs
        final int callingAppId = UserHandle.getAppId(state.mCallingUid);
        if (state.mCallingUid == Process.ROOT_UID
                || callingAppId == Process.SYSTEM_UID
                || callingAppId == Process.NFC_UID) {
            return new BalVerdict(
                    BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
                     "Important callingUid");
        }

        // Always allow home application to start activities.
        if (isHomeApp(state.mCallingUid, state.mCallingPackage)) {
            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ false,
                    "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 new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ false,
                    "Active ime");
        }

        // don't abort if the callingUid is a persistent system process
        if (state.mIsCallingUidPersistentSystemProcess) {
            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ false, "callingUid is persistent system process");
        }

        // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
        if (hasBalPermission(state.mCallingUid, state.mCallingPid)) {
            return new BalVerdict(BAL_ALLOW_PERMISSION,
                    /*background*/ true,
                    "START_ACTIVITIES_FROM_BACKGROUND permission granted");
        }
        // don't abort if the caller has the same uid as the recents component
        if (mSupervisor.mRecentTasks.isCallerRecents(state.mCallingUid)) {
            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ true, "Recents Component");
        }
        // don't abort if the callingUid is the device owner
        if (mService.isDeviceOwner(state.mCallingUid)) {
            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ true, "Device Owner");
        }
        // don't abort if the callingUid is a affiliated profile owner
        if (mService.isAffiliatedProfileOwner(state.mCallingUid)) {
            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ true, "Affiliated Profile Owner");
        }
        // don't abort if the callingUid has companion device
        final int callingUserId = UserHandle.getUserId(state.mCallingUid);
        if (mService.isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                    /*background*/ true, "Companion App");
        }
        // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
                state.mCallingPackage)) {
            Slog.w(
                    TAG,
                    "Background activity start for "
                            + state.mCallingPackage
                            + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
                    /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
        }
        // don't abort if the callingUid and callingPackage have the
        // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
        if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
                state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) {
            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
                    "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop 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.
        // Don't abort if the callerApp or other processes of that uid are allowed in any way.
        BalVerdict callerAppAllowsBal = checkProcessAllowsBal(state.mCallerApp, state);
        if (callerAppAllowsBal.allows()) {
            return callerAppAllowsBal;
        }

        // If we are here, it means all exemptions based on the creator failed
        return BalVerdict.BLOCK;
    }

通过看上面代码明显可以发现,相比google的文档条件,代码中的调节还要多一些,比如以下几种情况也是可以的:

1、具体如下权限

android.permission.START_ACTIVITIES_FROM_BACKGROUND

但是该权限是需要有系统签名等才可以获取,普通第三方app是没办法获取的


    <!-- @SystemApi @hide Allows an application to start activities from background -->
    <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
        android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role" />

所以对于系统应用而言,如果想要后台启动,只需要在自己的apk中添加权限既可以,注意同时要有系统签名或其他条件

2、如果app的uid属于Process.ROOT_UID, Process.SYSTEM_UID, Process.NFC_UID
这些都是属于重要的核心进程,所以可以直接允许后台启动Activity

3、如果是被一个persistent system process调用启动的
…其他一些条件。

不过也可以看出这些代码中的额外一些条件,其实都不是普通第三方app可以的达到的条件,所以文档中也没有列出这些,文档主要还是列出第三方应用可以达到条件。

后台启动Activity最简单处理方案

根据上面列出的app可以后台启动Activity条件,这里挑出2个最简单直接方式

方案1

针对系统app的情况

给自己的AndroidManifest.xml添加START_ACTIVITIES_FROM_BACKGROUND权限
AndroidManifest.xml

<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>

但是这种需要属于系统app才可以这样做的,因为一般这个权限需要系统签名等。

方案2

这个较为简单,属于第三方app和系统app都可以用的方案。

声明SYSTEM_ALERT_WINDOW权限,这个就是常用app显示悬浮窗口都需要声明的权限。

AndroidManifest.xml

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

这权限不需要系统签名等,普通第三方app可以获取,但是需要引导用户去设置选项中开启一下:
在这里插入图片描述
如果系统app本身有签名的话,经过验证不需要进行手动开启也可以。

更多framework实战开发干货,请关注下面“千里马学框架”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值