我是靠谱客的博主 现实流沙,这篇文章主要介绍前台服务豁免的源码阅读记录(Android 12版本),现在分享给大家,希望可以做个参考。

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

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

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

  1. 进程为 PROCESS_STATE_TOP (进程持有当前的顶层activity。注意,这涵盖了所有对用户可见的activity。或者该进程为系统进程)
复制代码
1
2
3
4
5
6
final int uidState = mAm.getUidStateLocked(callingUid); // Is the calling UID at PROCESS_STATE_TOP or above? if (uidState <= PROCESS_STATE_TOP) { ret = getReasonCodeFromProcState(uidState); }
复制代码
1
2
3
4
5
6
7
/** @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常量控制)
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
复制代码
1
2
3
4
5
6
7
if (ret == REASON_DENIED) { if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; } }
  1. 是否使用了CDM(Companion Device Manager)并声明了REQUEST_COMPANION_RUN_IN_BACKGROUND等权限
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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 中(对应用取消电池优化限制)
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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
复制代码
1
2
3
4
5
6
if (ret == REASON_DENIED) { if (UserManager.isDeviceInDemoMode(mAm.mContext)) { ret = REASON_DEVICE_DEMO_MODE; } }
  1. profile owner的app(如Lancher)
复制代码
1
2
3
4
5
6
7
8
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
复制代码
1
2
3
4
5
6
7
8
9
10
11
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
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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
复制代码
1
2
3
4
5
6
7
8
if (ret == REASON_DENIED) { if (mAm.mConstants.mFgsAllowOptOut && targetService != null && targetService.appInfo.hasRequestForegroundServiceExemption()) { ret = REASON_OPT_OUT_REQUESTED; } }

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

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

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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异常了

复制代码
1
2
3
4
5
6
7
8
9
10
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异常了

复制代码
1
2
3
4
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + context.getPackageName())); startActivityForResult(intent, 0);

最后

以上就是现实流沙最近收集整理的关于前台服务豁免的源码阅读记录(Android 12版本)的全部内容,更多相关前台服务豁免的源码阅读记录(Android内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部