前台服务豁免的源码阅读记录(Android 12版本)

137 阅读2分钟

Android 12 对前台服务增加了一些限制,在为 target 31 适配过程中阅读了一些AOSP的系统源码,本文用于记录这些阅读。

当应用在禁止启动前台服务的情况下调用Service.startForegroundContext.startForegroundService方法时会出现ForegroundServiceStartNotAllowedException异常,这个异常只有在应用的targetSdkVersion在31以上时会出现

而在后台启动前台服务的豁免情况在ActiveService.shouldAllowFgsStartForegroundLocked方法中的有描述,具体情况如下:

  1. 进程为 PROCESS_STATE_TOP (进程持有当前的顶层activity。注意,这涵盖了所有对用户可见的activity。或者该进程为系统进程)
final int uidState = mAm.getUidStateLocked(callingUid);
// Is the calling UID at PROCESS_STATE_TOP or above?
if (uidState <= PROCESS_STATE_TOP) {
    ret = getReasonCodeFromProcState(uidState);
}
/** @hide Process is hosting the current top activities.  Note that this covers
  * all activities that are visible to the user. */
@UnsupportedAppUsage
@TestApi
public static final int PROCESS_STATE_TOP = ProcessStateEnum.TOP;
  1. 当前进程状态允许启动前台服务(PROCESS_STATE_BOUND_FOREGROUND_SERVICE相关,Process is hosting a foreground service due to a system binding.) | 进程或应用有从后台启动前台服务的权限START_FOREGROUND_SERVICES_FROM_BACKGROUND(这个权限第三方不可用,仅限系统app) | 应用到后台没超过5秒(DEFAULT_FG_TO_BG_FGS_GRACE_DURATION常量控制)
if (ret == REASON_DENIED) {
    final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
        if (app.uid == callingUid) {
            final ProcessStateRecord state = app.mState;
            if (state.isAllowedStartFgsState()) {
                return getReasonCodeFromProcState(state.getAllowStartFgsState());
            } else {
                final ActiveInstrumentation instr = app.getActiveInstrumentation();
                if (instr != null
                        && instr.mHasBackgroundForegroundServiceStartsPermission) {
                    return REASON_INSTR_BACKGROUND_FGS_PERMISSION;
                }
                final long lastInvisibleTime = app.mState.getLastInvisibleTime();
                if (lastInvisibleTime > 0 && lastInvisibleTime < Long.MAX_VALUE) {
                    final long sinceLastInvisible = SystemClock.elapsedRealtime()
                            - lastInvisibleTime;
                    if (sinceLastInvisible < mAm.mConstants.mFgToBgFgsGraceDuration) {
                        return REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
                    }
                }
            }
        }
        return null;
    });
    if (allowedType != null) {
        ret = allowedType;
    }
}

if (ret == REASON_DENIED) {
    if (mAm.checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid,
            callingUid) == PERMISSION_GRANTED) {
        ret = REASON_BACKGROUND_FGS_PERMISSION;
    }
}
  1. 应用有系统弹窗权限 SYSTEM_ALERT_WINDOW
if (ret == REASON_DENIED) {
    if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
            callingPackage)) {
        ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
    }
}

4. 是否使用了CDM(Companion Device Manager)并声明了REQUEST_COMPANION_RUN_IN_BACKGROUND等权限

// Check for CDM apps with either REQUEST_COMPANION_RUN_IN_BACKGROUND or
// REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND.
// Note: When a CDM app has REQUEST_COMPANION_RUN_IN_BACKGROUND, the app is also put
// in the user-allowlist. However, in this case, we want to use the reason code
// REASON_COMPANION_DEVICE_MANAGER, so this check needs to be before the
// isAllowlistedForFgsStartLOSP check.
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;
        }
    }
}
  1. 应用是否在 Power-save allowlisted app-ids 中(对应用取消电池优化限制)
if (ret == REASON_DENIED) {
    ActivityManagerService.FgsTempAllowListItem item =
            mAm.isAllowlistedForFgsStartLOSP(callingUid);
    if (item != null) {
        if (item == ActivityManagerService.FAKE_TEMP_ALLOW_LIST_ITEM) {
            ret = REASON_SYSTEM_ALLOW_LISTED;
        } else {
            ret = item.mReasonCode;
        }
    }
}
  1. demo模式下的app
if (ret == REASON_DENIED) {
    if (UserManager.isDeviceInDemoMode(mAm.mContext)) {
        ret = REASON_DEVICE_DEMO_MODE;
    }
}
  1. profile owner的app(如Lancher)
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;
    }
}
  1. VPN的app
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;
    }
}
  1. 当前输入法的app
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;
        }
    }
}
  1. 目标服务已获得前台服务的豁免,PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION 私有API
if (ret == REASON_DENIED) {
    if (mAm.mConstants.mFgsAllowOptOut
            && targetService != null
            && targetService.appInfo.hasRequestForegroundServiceExemption()) {
        ret = REASON_OPT_OUT_REQUESTED;
    }
}

Android 12 上在后台启动前台服务的几种方案

利用精确闹钟启动前台服务

这种方法在target31时需要声明精确闹钟权限

AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

PendingIntent alarmIntent;
Intent intent = new Intent(context, MyForegroundService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    alarmIntent = PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
} else {
    alarmIntent = PendingIntent.getService(context, 0, intent, 0);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC, System.currentTimeMillis() + duration, alarmIntent);
} else {
    alarmMgr.setExact(AlarmManager.RTC, System.currentTimeMillis() + duration, alarmIntent);
}

取消电池优化

取消电池优化后,原来的启动方法就不会报出ForegroundServiceStartNotAllowedException异常了

PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    boolean ignoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(getPackageName());
    if (!ignoringBatteryOptimizations) {
        Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivity(intent);
    }
}

获取系统弹窗权限

获取系统弹窗权限后,原来的启动方法就不会报出ForegroundServiceStartNotAllowedException异常了

Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + context.getPackageName()));
startActivityForResult(intent, 0);