android 12+从后台启动FGS限制

news2024/11/19 0:38:12

后台启动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权限

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

申请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" />

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue前端打包Docker镜像并nginx运行

首先说明咱们的前端项目是基于Vue的&#xff0c;反向代理使用的是nginx 1.打包vue前端项目生成dist文件夹上传至服务器 新建一个文件夹&#xff0c;叫vueDockerTest&#xff0c;下面的文件都需要。 cert是你存放ssl证书的文件夹&#xff0c;nginx.conf 是nginx的配置文件&am…

Kotlin 惰性集合操作-序列 Sequence

集合操作函数 和 序列 在了解 Kotlin 惰性集合之前&#xff0c;先看一下 Koltin 标注库中的一些集合操作函数。 定义一个数据模型 Person 和 Book 类&#xff1a; data class Person(val name: String, val age: Int) data class Book(val title: String, val authors: List…

jmeter 5.5+influxdb 2.0+grafana v9.3.2 - 压测看板setup

Docker set up 安装docker应用 https://docs.docker.com/desktop/install/mac-install/&#xff0c;在官网下载docker安装包&#xff0c;和安装其他的mac应用是一样的操作。 设置国内的镜像仓库&#xff08;拉取镜像会快很多&#xff09; {"registry-mirrors": [&q…

叠氮-聚乙二醇-羧酸;叠氮-单乙二醇-丙酸Azido-PEG1-acid;1393330-34-1小分子PEG衍生物

Azido-PEG1-acid 中文名称&#xff1a;叠氮-聚乙二醇-羧酸&#xff1b;叠氮-单乙二醇-丙酸 英文名称&#xff1a;Azido-PEG1-acid&#xff1b; 分子式&#xff1a;C5H9N3O3 分子量 &#xff1a;159.1 CAS&#xff1a;1393330-34-1 外观&#xff1a;粘稠液体或者固体粉末&#…

SHA和AES加密+GUI Swing写的一个本地运行和保存的密码管理小工具

目录效果项目结构功能1、登录2、加密3、解密4、列表代码1、先准备好两种加密方式的工具类SHAUtilAESUtil2、登录窗口3、主页窗口&#xff08;加密和解密面板&#xff09;4、主页窗口&#xff08;列表面板&#xff09;5、主程序&#xff08;main&#xff09;最后通过SHA和AES加密…

TestStand-序列步骤属性

文章目录GeneralRun OptionLoopingPost ActionSwitchingSynchronizationExpressionPreconditionsRequirementAdditional ResultPropertyCtrl-N创建一个新的Sequence&#xff0c;通过右键创建任意步骤 General Name -步骤的名称。 Type -步骤类型。一般不需要设置。 Adapter-适…

Android Kotlin之协程-异步流Flow的使用

数据流以协程为基础构建&#xff0c;与仅返回单个值的挂起函数相反&#xff0c;数据流可按顺序发出多个值。从概念上来讲&#xff0c;数据流是可通过异步方式进行计算处理的一组数据序列。所发出值的类型必须相同。 数据流包含三个实体&#xff1a; 提供方会生成添加到数据流…

信息安全技术 政务信息共享 数据安全技术要求

声明 本文是学习GB-T 39477-2020 信息安全技术 政务信息共享 数据安全技术要求. 下载地址 http://github5.com/view/790而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 政务信息共享 数据安全 范围 本标准提出了政务信息共享数据安全要求技术框架&…

2023年工作第一天心情感悟

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 今天是2023年1月3日&#xff0c;也是我们上班的第一天。今天这篇随记&#xff0c;也发表下我对2023年的看法&#xff0c;也对过去的2022年做过总结。 &#xff08;2023年元旦&#xff0c;到门头沟…

Spring之ApplicationContext快速入门

目录 一&#xff1a;概述 二&#xff1a;代码演示 三&#xff1a;BeanFactory与ApplicationContext的关系 四&#xff1a;BeanFactory的继承体系 五&#xff1a;ApplicationContext的继承体系 一&#xff1a;概述 ApplicationContext称为Spring容器&#xff0c; 内部封装了…

面试官:能用JavaScript手写一个bind函数吗

经常会看到网上各种手写bind的教程&#xff0c;下面是我在自己实现手写bind的过程中遇到的问题与思考。如果对于如何实现一个手写bind还有疑惑的话&#xff0c;那么可以先看看上面两篇文章。 手写bind vs 原生bind 我们先使用一个典型的手写bind的例子&#xff0c;代码如下&a…

PHP命令执行的函数

在做面试题的时候发现&#xff0c;自己对PHP命令执行的函数的了解并不是很全面&#xff0c;就想这去学习一下。我也在网上找到了许多的资料&#xff0c;在这里我就相当于一个总结吧。 system(); System()函数的主要功能是在系统权限允许的情况是执行系统命令,windows系统和Lin…

【服务器数据恢复】EMC存储Zfs文件系统下raid5数据恢复案例

服务器存储数据恢复环境&#xff1a; 某公司一台EMC存储&#xff0c;12块硬盘组成raid5&#xff0c;2块热备盘&#xff1b; Zfs文件系统。 服务器存储故障&#xff1a; 硬盘故障导致存储崩溃。 服务器存储数据恢复过程&#xff1a; 1、对故障存储所有硬盘进行物理故障检测&…

详细软件著作权的申请

一&#xff0c;申请注册账号并进行实名认证 在中国版权保护中心官网注册账号。 我是自己申请的所以选择的个人&#xff0c;这里根据实际情况进行选择后注册。 注册后进行实名认证&#xff08;3-7个工作日人工会进行审核&#xff0c;所以每个著作权人都要提前注册并进行实名认证…

论文投稿指南——中文核心期刊推荐(地球物理学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

【电商】电商后台---商品上架前的最后准备

电商后台相关模块进行维护后&#xff0c;离商品上架越来越近。 在供应商、合同、商品、价税等都维护完成后&#xff0c;采购部创建采购单&#xff0c;离商品可以上架销售越来越近了。 本篇再接着梳理一下商品销售前的最后准备工作&#xff08;没考虑促销&#xff09;&#xff…

P1111 修复公路

题目背景 AA地区在地震过后&#xff0c;连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。 题目描述 给出A地区的村庄数NN&#xff0c;和公路数MM&#xff0c;公路是双向的。并告诉你每条公路的连着哪两个村庄&#xff0c;并告诉你什么时候能修完这条公路。问…

Python-123练习-04简单分支

文章目录1. 判断闰年2. 今年多少天3. 今天是第几天4. 判断奇偶数5. 计算整数 n 的阶乘6. 判断是否直角三角形7. 判断三角形并计算面积8. 出租车计费9. 一元二次方程求根10. 个税计算器11. 分期付款计算器12. 字符大小写转换1. 判断闰年 描述‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪…

学习HTTP协议,这一篇就够啦

HTTP协议一、什么是HTTP1.1 应用层协议1.2 HTTP1.3 HTTP协议的工作过程二、HTTP协议格式2.1 Fiddler抓包工具2.2 协议格式三、HTTP请求 (Request)3.1 认识 "方法" (method)3.1.1 GET 方法3.1.2 POST 方法3.1.3 GET和POST比较3.1.4 其他方法3.2 认识URL3.2.1 URL基本格…

Elasticsearch:使用 Node.js 将实时数据提取到 Elasticsearch 中(一)

Elasticsearch 是一个强大的 RESTful 搜索和分析引擎&#xff0c;能够处理越来越多的用例。 它将集中存储你的数据&#xff0c;以实现闪电般的快速搜索、微调相关性以及可轻松扩展的强大分析。 关于如何使用 Elastic Stack&#xff08;又名 ELK 堆栈&#xff09;将数据摄取到 E…