android 12+从后台启动FGS限制

自Android12开始,应用在后台运行时无法启动前台服务,除非满足特定豁免条件。这些限制旨在优化系统性能和电池寿命。当尝试从后台启动前台服务时,系统会抛出异常。豁免情况包括应用在前台、具有特定权限的应用、系统组件等。开发者应考虑使用WorkManager替代,并检查应用是否符合豁免条件或申请相应权限。
摘要由CSDN通过智能技术生成

后台启动FGS限制

限制简介

以 Android 12(API 级别 31)或更高版本为目标平台的应用在后台运行时无法启动前台服务,少数特殊情况除外。 如果应用程序在后台运行时尝试启动前台服务,而前台服务不满足其中一种异常情况,系统将抛出 ForegroundServiceStartNotAllowedException。
注意:如果一个应用调用 Context.startForegroundService() 来启动另一个应用拥有的前台服务,则这些限制仅适用于两个应用都以 Android 12 或更高版本为目标的情况。

错误日志如下

12-17 01:14:55.156 1383 12145 W ActivityManager: Background started FGS: Disallowed [callingPackage: com.debug.loggerui; callingUid: 10102; uidState: SVC ; intent: Intent { cmp=com.debug.loggerui/.framework.DebugLoggerUIService }; code:DENIED; tempAllowListReason:; targetSdkVersion:31; callerTargetSdkVersion:31; startForegroundCount:0; bindFromPackage:null]

java.lang.RuntimeException: Unable to create service com.debug.loggerui.framework.DebugLoggerUIService: android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service com.debug.loggerui/.framework.DebugLoggerUIService

豁免日志如下:

12-21 01:01:17.121 2399 3193 I am_wtf : [0,2399,system_server,-1,ActivityManager,Background started FGS: Allowed [callingPackage: com.android.providers.contacts; callingUid: 10071; uidState: BFGS; intent: Intent { act=android.intent.action.SIM_STATE_CHANGED cmp=com.android.providers.contacts/com.miui.providers.contacts.sim.SimStateChangedService (has extras) }; code:PROC_STATE_BFGS; tempAllowListReason:<,reasonCode:SYSTEM_ALLOW_LISTED,duration:9223372036854775807,callingUid:-1>; targetSdkVersion:33; callerTargetSdkVersion:33; startForegroundCount:0; bindFromPackage:null]]

限制原理

setFgsRestrictionLocked

FGS 有两个限制:
在 R 中,mAllowWhileInUsePermissionInFgs 是允许在前台服务中使用 while-in-use 权限。 从后台启动的 FGS 中的使用中权限可能会受到限制。 具体见 Bg start FGS的while in use权限
在S中,mAllowStartForeground是允许FGS是否startForeground。 从后台启动的服务可能不会成为 FGS。

启动或绑定或调用startForeground时会调用setFgsRestrictionLocked方法去校验上面的两个FGS限制。
在这里插入图片描述

private void setFgsRestrictionLocked(String callingPackage,
        int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
        boolean allowBackgroundActivityStarts) {
    	.......
    if (!r.mAllowWhileInUsePermissionInFgs
            || (r.mAllowStartForeground == REASON_DENIED)) {
        // while in use权限校验
    	......
        // 是否允许后台启动FGS校验
        if (r.mAllowStartForeground == REASON_DENIED) {
            r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                    allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,userId);
            }
        }
    }
shouldAllowFgsStartForegroundWithBindingCheckLocked

mAllowStartForeground由shouldAllowFgsStartForegroundWithBindingCheckLocked方法返回值赋值;并计算赋值mInfoAllowStartForeground,以便后面打印相关信息。

是否应该允许 FGS 启动(又名 startForeground())

具体豁免情况见下面的 后台启动限制的豁免

private @ReasonCode int shouldAllowFgsStartForegroundWithBindingCheckLocked(
        @ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
        int callingUid, Intent intent, ServiceRecord r, int userId) {
    ActivityManagerService.FgsTempAllowListItem tempAllowListReason =
            // mDeviceIdleExceptIdleAllowlist 或 mFgsStartTempAllowList列表中
            r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
    // 见“后台启动限制的豁免”
    int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,callingUid, callingPackage, r);

    String bindFromPackage = null;
    // 查看client是否允许start FGS
    if (ret == REASON_DENIED) {
        bindFromPackage = canBindingClientStartFgsLocked(callingUid);
        if (bindFromPackage != null) {
            ret = REASON_FGS_BINDING;
        }
    }

    final int uidState = mAm.getUidStateLocked(callingUid);
    int callerTargetSdkVersion = -1;
    try {
        callerTargetSdkVersion = mAm.mContext.getPackageManager()
                .getTargetSdkVersion(callingPackage);
    } catch (PackageManager.NameNotFoundException ignored) {
    }
    final String debugInfo =
        // calling 相关信息
            "[callingPackage: " + callingPackage
                    + "; callingUid: " + callingUid
                    + "; uidState: " + ProcessList.makeProcStateString(uidState)
                    // service信息
                    + "; intent: " + intent
                    // 豁免reson code打印
                    + "; code:" + reasonCodeToString(ret)
                    // 打印临时白名单的相关信息
                    + "; tempAllowListReason:<"
                    + (tempAllowListReason == null ? null :
                            (tempAllowListReason.mReason
                                    + ",reasonCode:"
                                    + reasonCodeToString(tempAllowListReason.mReasonCode)
                                    + ",duration:" + tempAllowListReason.mDuration
                                    + ",callingUid:" + tempAllowListReason.mCallingUid))
                    + ">"
                    + "; targetSdkVersion:" + r.appInfo.targetSdkVersion
                    + "; callerTargetSdkVersion:" + callerTargetSdkVersion
                    + "; startForegroundCount:" + r.mStartForegroundCount
                    + "; bindFromPackage:" + bindFromPackage
                    + "]";
    // 赋值mInfoAllowStartForeground以便在logFgsBackgroundStart 打印这些信息
    if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
        r.mLoggedInfoAllowStartForeground = false;
        r.mInfoAllowStartForeground = debugInfo;
    }
    return ret;
}
logFgsBackgroundStart

启动Service时,如果是FGS则会去校验是否允许本次启动,后台启动FGS是否豁免等;如果不满足条件会抛出如上异常。

        if (fgRequired) {
            // 打印Background started FGS相关log,无论是否允许启动都会打印
            logFgsBackgroundStart(r);
            if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
                String msg = "startForegroundService() not allowed due to "
                        + "mAllowStartForeground false: service "
                        + r.shortInstanceName;
                // 打印出错信息
                Slog.w(TAG, msg);
                showFgsBgRestrictedNotificationLocked(r);
                logFGSStateChangeLocked(r,
                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
                        0, FGS_STOP_REASON_UNKNOWN);
                // 抛出异常
                if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
                    throw new ForegroundServiceStartNotAllowedException(msg);
                }
                return null;
            }
        }

如果是后台启动FGS,则无论是否豁免均会打印Background started FGS相关log

    private void logFgsBackgroundStart(ServiceRecord r) {
        // Only log if FGS is started from background.
        if (!isFgsBgStart(r.mAllowStartForeground)) {
            return;
        }
        if (!r.mLoggedInfoAllowStartForeground) {
            // 主要豁免信息等保存在mInfoAllowStartForeground中
            final String msg = "Background started FGS: "
                    + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
                    + r.mInfoAllowStartForeground;
            if (r.mAllowStartForeground != REASON_DENIED) {
                if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
                        mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
                    Slog.wtfQuiet(TAG, msg);
                }
                Slog.i(TAG, msg);
            } else {
                if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
                        mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
                    Slog.wtfQuiet(TAG, msg);
                }
                Slog.w(TAG, msg);
            }
            // 打印过后赋值为true
            r.mLoggedInfoAllowStartForeground = true;
        }
    }

推荐解决方案

如果您发现您的应用在从后台运行时启动前台服务,请更新您的应用逻辑以使用 WorkManager。 要查看如何更新您的应用程序的示例,请查看 GitHub 上的 WorkManagerSample。

检查您的应用是否执行后台启动

为了更好地了解您的应用在后台运行时何时尝试启动前台服务,您可以启用每次出现此行为时显示的通知。 为此,请在连接到测试设备或模拟器的开发机器上执行以下 ADB 命令:
adb shell device_config put activity_manager default_fgs_starts_restriction_notification_enabled true

后台启动限制的豁免

    /**
     * The list of BG-FGS-Launch and temp-allow-list reason code.
     * @hide
     */
    @IntDef(flag = true, prefix = { "REASON_" }, value = {
            // BG-FGS-Launch reasons.
            REASON_DENIED,
            REASON_UNKNOWN,
            REASON_OTHER,
            // 前台procState
            REASON_PROC_STATE_PERSISTENT,
            REASON_PROC_STATE_PERSISTENT_UI,
            REASON_PROC_STATE_TOP,
            REASON_PROC_STATE_BTOP,
            REASON_PROC_STATE_FGS,
            REASON_PROC_STATE_BFGS,
            // 有可见的window
            REASON_UID_VISIBLE,
            // 特殊uid
            REASON_SYSTEM_UID,
            REASON_ACTIVITY_STARTER,
            // pendingIntent通知
            REASON_START_ACTIVITY_FLAG,
            // service的client能从后台启动FGS
            REASON_FGS_BINDING,
            REASON_DEVICE_OWNER,
            // 资料所有者
            REASON_PROFILE_OWNER,
            // 应用使用配套设备管理器并声明
            REASON_COMPANION_DEVICE_MANAGER,
            // bg activity权限
            REASON_BACKGROUND_ACTIVITY_PERMISSION,
            // fgs权限
            REASON_BACKGROUND_FGS_PERMISSION,
            // bg activity权限的instr
            REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION,
            // fgs权限的instr
            REASON_INSTR_BACKGROUND_FGS_PERMISSION, 
            // 悬浮窗权限
            REASON_SYSTEM_ALERT_WINDOW_PERMISSION,
            // 演示模式
            REASON_DEVICE_DEMO_MODE,
            // while-in-use
            REASON_ALLOWLISTED_PACKAGE,
            REASON_APPOP,
            // 5s内可见
            REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD, 
            // 应用获得允许ACTIVATE_VPN或ACTIVATE_PLATFORM_VPN权限
            REASON_OP_ACTIVATE_VPN,
            REASON_OP_ACTIVATE_PLATFORM_VPN,
            // 应用是设备当前的输入法
            REASON_CURRENT_INPUT_METHOD,
            // while-in-use
            REASON_TEMP_ALLOWED_WHILE_IN_USE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ReasonCode {}

在以下情况下,即使您的应用程序在后台运行,您的应用程序也可以启动前台服务:

@PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
应用程序在前台
  • REASON_PROC_STATE_PERSISTENT
  • REASON_PROC_STATE_PERSISTENT_UI
  • REASON_PROC_STATE_TOP
    private @ReasonCode int shouldAllowFgsStartForegroundNoBindingCheckLocked(
            @ReasonCode int allowWhileInUse, int callingPid, int callingUid, String callingPackage,
            @Nullable ServiceRecord targetService) {
        int ret = allowWhileInUse;

        if (ret == REASON_DENIED) {
            final int uidState = mAm.getUidStateLocked(callingUid);
            // Is the calling UID at PROCESS_STATE_TOP or above?
            if (uidState <= PROCESS_STATE_TOP) {
                ret = getReasonCodeFromProcState(uidState);
            }
        }
  • REASON_PROC_STATE_BTOP
  • REASON_PROC_STATE_FGS
  • REASON_PROC_STATE_BFGS
if (ret == REASON_DENIED) {
    final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
        if (app.uid == callingUid) {
            final ProcessStateRecord state = app.mState;
            if (state.isAllowedStartFgs()) {
                return getReasonCodeFromProcState(state.getCurProcState());
            } .......
    @GuardedBy("mService")
    boolean isAllowedStartFgs() {
        return mCurProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
    }
应用程序可见
  • REASON_UID_VISIBLE
        if (ret == REASON_DENIED) {
            // Does the calling UID have any visible activity?
            final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
            if (isCallingUidVisible) {
                ret = REASON_UID_VISIBLE;
            }
        }
该服务通过与通知交互来启动
该服务由不同的可见应用程序发送的 PendingIntent 启动

细节可参考 BackgroundLaunchProcessController 介绍

  • REASON_START_ACTIVITY_FLAG
        if (ret == REASON_DENIED) {
            // Is the allow activity background start flag on?
            if (allowBackgroundActivityStarts) {
                ret = REASON_START_ACTIVITY_FLAG;
            }
        }
该服务由系统组件启动(root/system/nfc/shell)
  • REASON_SYSTEM_UID
        if (ret == REASON_DENIED) {
            boolean isCallerSystem = false;
            final int callingAppId = UserHandle.getAppId(callingUid);
            switch (callingAppId) {
                case ROOT_UID:
                case SYSTEM_UID:
                case NFC_UID:
                case SHELL_UID:
                    isCallerSystem = true;
                    break;
                default:
                    isCallerSystem = false;
                    break;
            }

            if (isCallerSystem) {
                ret = REASON_SYSTEM_UID;
            }
        }
该服务由具有 START_ACTIVITIES_FROM_BACKGROUND 特权权限的应用程序启动
  • REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION
  • REASON_BACKGROUND_ACTIVITY_PERMISSION
        if (ret == REASON_DENIED) {
            if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                    == PERMISSION_GRANTED) {
                ret = REASON_BACKGROUND_ACTIVITY_PERMISSION;
            }
        }

        if (ret == REASON_DENIED) {
            if (targetService != null && targetService.app != null) {
                ActiveInstrumentation instr = targetService.app.getActiveInstrumentation();
                if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
                    ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
                }
            }
        }
该服务由具有 START_FOREGROUND_SERVICES_FROM_BACKGROUND 特权权限的应用程序启动。
  • REASON_BACKGROUND_FGS_PERMISSION
  • REASON_INSTR_BACKGROUND_FGS_PERMISSION
if (ret == REASON_DENIED) {
    final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
        if (app.uid == callingUid) {
            final ProcessStateRecord state = app.mState;
            if (state.isAllowedStartFgs()) {
              .......
            } else {
                final ActiveInstrumentation instr = app.getActiveInstrumentation();
                if (instr != null
                        && instr.mHasBackgroundForegroundServiceStartsPermission) {
                    // 调用者是否拥有 START_FOREGROUND_SERVICES_FROM_BACKGROUND 权限
                    return REASON_INSTR_BACKGROUND_FGS_PERMISSION;
                }
               ......
    	if (ret == REASON_DENIED) {
            if (mAm.checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid,callingUid) == PERMISSION_GRANTED) {
                ret = REASON_BACKGROUND_FGS_PERMISSION;
            }
        }

应用在5s内可见
  • REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD
if (ret == REASON_DENIED) {
    final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
        if (app.uid == callingUid) {
            final ProcessStateRecord state = app.mState;
            if (state.isAllowedStartFgs()) {
              .......
            } else {
              .......
                final long lastInvisibleTime = app.mState.getLastInvisibleTime();
                if (lastInvisibleTime > 0 && lastInvisibleTime < Long.MAX_VALUE) {
                    final long sinceLastInvisible = SystemClock.elapsedRealtime()
                            - lastInvisibleTime;
                    // 5s
                    if (sinceLastInvisible < mAm.mConstants.mFgToBgFgsGraceDuration) {
                        return REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
                    }
                }
            }
        }
  ......
}
应用申请了SYSTEM_ALERT_WINDOW权限并在权限管理页面获得用户同意
  • REASON_SYSTEM_ALERT_WINDOW_PERMISSION
    	if (ret == REASON_DENIED) {
            if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
                    callingPackage)) {
                ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
            }
        }
应用使用配套设备管理器并声明REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND 权限或 REQUEST_COMPANION_RUN_IN_BACKGROUND 权限

尽可能使用 REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND。

  • REASON_COMPANION_DEVICE_MANAGER

注意:当 CDM 应用程序具有 REQUEST_COMPANION_RUN_IN_BACKGROUND 时,该应用程序也会被放入用户白名单中。 但是,在这种情况下,我们要使用原因代码 REASON_COMPANION_DEVICE_MANAGER,因此此检查需要在 isAllowlistedForFgsStartLOSP 检查之前进行。

        if (ret == REASON_DENIED) {
            final boolean isCompanionApp = mAm.mInternal.isAssociatedCompanionApp(
                    UserHandle.getUserId(callingUid), callingUid);
            if (isCompanionApp) {
                if (isPermissionGranted(
                        REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND,
                        callingPid, callingUid)
                        || isPermissionGranted(REQUEST_COMPANION_RUN_IN_BACKGROUND,
                        callingPid, callingUid)) {
                    ret = REASON_COMPANION_DEVICE_MANAGER;
                }
            }
        }
设备处于演示模式
  • REASON_DEVICE_DEMO_MODE
       if (ret == REASON_DENIED) {
            if (UserManager.isDeviceInDemoMode(mAm.mContext)) {
                ret = REASON_DEVICE_DEMO_MODE;
            }
        }

资料所有者
  • REASON_PROFILE_OWNER
    	if (ret == REASON_DENIED) {
            // Is the calling UID a profile owner app?
            final boolean isProfileOwner = mAm.mInternal.isProfileOwner(callingUid);
            if (isProfileOwner) {
                ret = REASON_PROFILE_OWNER;
            }
        }
应用获得允许ACTIVATE_VPN或ACTIVATE_PLATFORM_VPN权限
  • REASON_OP_ACTIVATE_VPN
  • REASON_OP_ACTIVATE_PLATFORM_VPN
        if (ret == REASON_DENIED) {
            final AppOpsManager appOpsManager = mAm.getAppOpsManager();
            if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, callingUid,
                    callingPackage) == AppOpsManager.MODE_ALLOWED) {
                ret = REASON_OP_ACTIVATE_VPN;
            } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
                    callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {
                ret = REASON_OP_ACTIVATE_PLATFORM_VPN;
            }
        }
应用是设备当前的输入法
  • REASON_CURRENT_INPUT_METHOD
if (ret == REASON_DENIED) {
    final String inputMethod =
            Settings.Secure.getStringForUser(mAm.mContext.getContentResolver(),
                    Settings.Secure.DEFAULT_INPUT_METHOD,
                    UserHandle.getUserId(callingUid));
    if (inputMethod != null) {
        final ComponentName cn = ComponentName.unflattenFromString(inputMethod);
        if (cn != null && cn.getPackageName().equals(callingPackage)) {
            ret = REASON_CURRENT_INPUT_METHOD;
        }
    }
}
应用程序是否已请求免除前台服务限制

R.styleable#AndroidManifestApplication_requestForegroundServiceExemption

  • REASON_OPT_OUT_REQUESTED
if (ret == REASON_DENIED) {
    if (mAm.mConstants.mFgsAllowOptOut
            && targetService != null
            && targetService.appInfo.hasRequestForegroundServiceExemption()) {
        ret = REASON_OPT_OUT_REQUESTED;
    }
}
    @TestApi
    public boolean hasRequestForegroundServiceExemption() {
        return (privateFlagsExt
                & ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION) != 0;
    }
Service的client应用能从后台启动FGS
  • REASON_FGS_BINDING
        String bindFromPackage = null;
        if (ret == REASON_DENIED) {
            bindFromPackage = canBindingClientStartFgsLocked(callingUid);
            if (bindFromPackage != null) {
                ret = REASON_FGS_BINDING;
            }
        }

关于areBackgroundActivityStartsAllowed系列

该服务由某个 Activity 刚在不久前启动/结束的应用启动
该服务由运行后台启动activity特权的Active Instrumentation的应用启动
该服务由在前台任务的返回栈中拥有 Activity的应用启动
该服务由某个服务被另一个可见应用绑定的应用启动

  • REASON_ACTIVITY_STARTER
if (ret == REASON_DENIED) {
    final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
        if (pr.uid == callingUid) {
        	if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
                return REASON_ACTIVITY_STARTER;
            }
        }
        return null;
    });
    if (allowedType != null) {
        ret = allowedType;
    }
}

应用在临时白名单

具体参考后台启动FGS的临时白名单

    @Nullable
    @GuardedBy(anyOf = {"this", "mProcLock"})
    FgsTempAllowListItem isAllowlistedForFgsStartLOSP(int uid) {
        if (Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0) {
            // 在mDeviceIdleExceptIdleAllowlist名单
        	return FAKE_TEMP_ALLOW_LIST_ITEM;
        }
    	// 在mFgsStartTempAllowList名单里
        final Pair<Long, FgsTempAllowListItem> entry = mFgsStartTempAllowList.get(uid);
        return entry == null ? null : entry.second;
    }
        if (ret == REASON_DENIED) {
            ActivityManagerService.FgsTempAllowListItem item =
                    mAm.isAllowlistedForFgsStartLOSP(callingUid);
            if (item != null) {
                // mDeviceIdleExceptIdleAllowlist  省电白名单
                if (item == ActivityManagerService.FAKE_TEMP_ALLOW_LIST_ITEM) {
                    ret = REASON_SYSTEM_ALLOW_LISTED;
                } else {
                    // mFgsStartTempAllowList,允许从后台启动 FGS 的临时许可名单。
                    ret = item.mReasonCode;
                }
            }
        }
REASON_SYSTEM_ALLOW_LISTED

您可以通过将用户发送到系统设置中您应用的应用信息页面来帮助用户找到此选项。为此,调用包含 ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS 意图操作的意图。

如上面代码,如果当前应用在mDeviceIdleExceptIdleAllowlist名单里,则豁免原因为REASON_SYSTEM_ALLOW_LISTED

REASON_GEOFENCING / REASON_ACTIVITY_RECOGNITION

您的应用收到与地理围栏或活动识别转换相关的事件。

REASON_NOTIFICATION_SERVICE

用户对与您的应用程序相关的 UI 元素执行操作。 例如,他们可能会与气泡、通知、小部件或活动进行交互。

REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED

您的应用调用确切的警报来完成用户请求的操作。

REASON_PUSH_MESSAGING

您的应用使用 Firebase 云消息传递接收高优先级消息。

REASON_LOCKED_BOOT_COMPLETED / REASON_PRE_BOOT_COMPLETED / REASON_BOOT_COMPLETED / REASON_PACKAGE_REPLACED

设备重启并在广播接收器中接收到 ACTION_BOOT_COMPLETED、ACTION_LOCKED_BOOT_COMPLETED 或 ACTION_MY_PACKAGE_REPLACED 意图操作后。

REASON_TIMEZONE_CHANGED / REASON_TIME_CHANGED / REASON_LOCALE_CHANGED

您的应用在广播接收器中接收 ACTION_TIMEZONE_CHANGED、ACTION_TIME_CHANGED 或 ACTION_LOCALE_CHANGED 意图操作。

REASON_BLUETOOTH_BROADCAST

您的应用程序接收需要 BLUETOOTH_CONNECT 或 BLUETOOTH_SCAN 权限的蓝牙广播。

REASON_SERVICE_LAUNCH

当前应用在30s内启动过FGS

如何申请豁免

权限相关

申请START_FOREGROUND_SERVICES_FROM_BACKGROUND权限

允许应用程序随时从后台启动前台服务。 此权限不适用于第三方应用程序,唯一的例外是该应用程序是否为默认短信应用程序。 否则,它只能由特权应用程序、应用程序验证器应用程序和具有任何 EMERGENCY 或 SYSTEM GALLERY 角色。

<uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
 <permissions>
     <privapp-permissions package="com.android.bluetooth">   
         <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
 </privapp-permissions>
申请START_ACTIVITIES_FROM_BACKGROUND 权限

特权system/priv-app/目录下的app才可以申请豁免

<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
<permissions>
    <privapp-permissions package="com.android.xxx">
        <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
     </privapp-permissions>
</permissions>
申请SYSTEM_ALERT_WINDOW权限

**注意:**在 Android 10(Go 版本)上运行的应用无法获得SYSTEM_ALERT_WINDOW权限
注意:如果应用程序以 API 级别 23 或更高级别为目标,则应用程序用户必须通过权限管理屏幕明确向应用程序授予此权限。

 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值