概述
Android 12 对前台服务增加了一些限制,在为 target 31 适配过程中阅读了一些AOSP的系统源码,本文用于记录这些阅读。
当应用在禁止启动前台服务的情况下调用Service.startForeground
或Context.startForegroundService
方法时会出现ForegroundServiceStartNotAllowedException
异常,这个异常只有在应用的targetSdkVersion
在31以上时会出现
而在后台启动前台服务的豁免情况在ActiveService.shouldAllowFgsStartForegroundLocked
方法中的有描述,具体情况如下:
- 进程为 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;
- 当前进程状态允许启动前台服务(
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;
}
}
- 应用有系统弹窗权限 SYSTEM_ALERT_WINDOW
if (ret == REASON_DENIED) {
if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
callingPackage)) {
ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
}
}
- 是否使用了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;
}
}
}
- 应用是否在 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;
}
}
}
- demo模式下的app
if (ret == REASON_DENIED) {
if (UserManager.isDeviceInDemoMode(mAm.mContext)) {
ret = REASON_DEVICE_DEMO_MODE;
}
}
- 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;
}
}
- 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;
}
}
- 当前输入法的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;
}
}
}
- 目标服务已获得前台服务的豁免,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);
最后
以上就是现实流沙为你收集整理的前台服务豁免的源码阅读记录(Android 12版本)的全部内容,希望文章能够帮你解决前台服务豁免的源码阅读记录(Android 12版本)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复