Android 12 对前台服务增加了一些限制,在为 target 31 适配过程中阅读了一些AOSP的系统源码,本文用于记录这些阅读。
当应用在禁止启动前台服务的情况下调用Service.startForeground
或Context.startForegroundService
方法时会出现ForegroundServiceStartNotAllowedException
异常,这个异常只有在应用的targetSdkVersion
在31以上时会出现
而在后台启动前台服务的豁免情况在ActiveService.shouldAllowFgsStartForegroundLocked
方法中的有描述,具体情况如下:
- 进程为 PROCESS_STATE_TOP (进程持有当前的顶层activity。注意,这涵盖了所有对用户可见的activity。或者该进程为系统进程)
复制代码
1
2
3
4
5
6final 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;
- 当前进程状态允许启动前台服务(
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
35if (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
复制代码
1
2
3
4
5
6
7if (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等权限
复制代码
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; } } }
- 应用是否在 Power-save allowlisted app-ids 中(对应用取消电池优化限制)
复制代码
1
2
3
4
5
6
7
8
9
10
11
12if (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
复制代码
1
2
3
4
5
6if (ret == REASON_DENIED) { if (UserManager.isDeviceInDemoMode(mAm.mContext)) { ret = REASON_DEVICE_DEMO_MODE; } }
- profile owner的app(如Lancher)
复制代码
1
2
3
4
5
6
7
8if (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
复制代码
1
2
3
4
5
6
7
8
9
10
11if (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
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13if (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
复制代码
1
2
3
4
5
6
7
8if (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
14AlarmManager 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
10PowerManager 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
4Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + context.getPackageName())); startActivityForResult(intent, 0);
最后
以上就是现实流沙最近收集整理的关于前台服务豁免的源码阅读记录(Android 12版本)的全部内容,更多相关前台服务豁免的源码阅读记录(Android内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复