通常在Service的onStartCommand()方法中回调startForeground()方法,避免10s超时发生ANR。
Context context = getApplicationContext();
Intent intent = new Intent(...); // Build the intent for the service
context.startForegroundService(intent);
startForeground()方法中需要传入唯一标识通知的正整数id,跟通知本身
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
// Notification ID cannot be 0.
startForeground(ONGOING_NOTIFICATION_ID, notification);
调用stopForeground()将Service从前台状态移除,Service本身仍然继续运行;int值参数表明是否移除与该前台服务有关的通知。
如果在前台服务运行时停止该服务,则与之相关的通知会被移除。
https://developer.android.com/guide/components/foreground-services#notification-immediate
android 12+系统会等待10s才显示跟前台Service有关的通知(为短期运行的FGS提供优化体验),以下情况可以豁免(即立刻显示通知):
// 对应上面2
// Foreground service types that always get immediate notification display,
// expressed in the same bitmask format that ServiceRecord.foregroundServiceType
// uses.
static final int FGS_IMMEDIATE_DISPLAY_MASK =
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
| ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
| ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
| ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
public boolean shouldShowForegroundImmediately() {
// Has the app demanded immediate display?
// 对应上面4
if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
return true;
}
// Has the app demanded deferred display?
if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
return false;
}
// 对应上面3
// We show these sorts of notifications immediately in the absence of
// any explicit app declaration
if (isMediaNotification() || hasMediaSession()
|| CATEGORY_CALL.equals(category)
|| CATEGORY_NAVIGATION.equals(category)
// 对应上面1
|| (actions != null && actions.length > 0)) {
return true;
}
// No extenuating circumstances: defer visibility
return false;
}
通知入队时确定是否延迟显示,延迟显示再更新下Service的通知
https://developer.android.com/about/versions/13/changes/notification-permission
Android 13 中引入了新的运行时权限,用于从应用发送非豁免通知:POST_NOTIFICATIONS。此更改有助于用户专注于最重要的通知。
https://developer.android.com/guide/components/foreground-services#request-foreground-service-permissions
未在功能请单声明权限的话,启动时会抛出安全异常:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application ...>
...
application>
manifest>
https://developer.android.com/about/versions/13/changes/notification-permission
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
...
application>
manifest>
https://developer.android.com/guide/components/foreground-services#bg-access-restrictions
为了保护用户隐私,当前台Service在后台运行时,有以下限制:
// allow while-in-use permissions in foreground service or not.
// while-in-use permissions in FGS started from background might be restricted.
boolean mAllowWhileInUsePermissionInFgs;
不满足豁免条件下,系统会有如下log打印:
Foreground service started from background can not have location/camera/microphone access: service SERVICE_NAME
豁免:
https://www.yuque.com/amytan-l2rhk/qstog7/ryb5tq/
/**
* Should allow while-in-use permissions in FGS or not.
* A typical BG started FGS is not allowed to have while-in-use permissions.
* @param callingPackage caller app's package name.
* @param callingUid caller app's uid.
* @param targetService the service to start.
* @return {@link ReasonCode}
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
int callingPid, int callingUid, @Nullable ServiceRecord targetService,
boolean allowBackgroundActivityStarts) {
int ret = REASON_DENIED;
// 非后台,进程优先级较高
final int uidState = mAm.getUidStateLocked(callingUid);
if (ret == REASON_DENIED) {
// Is the calling UID at PROCESS_STATE_TOP or above?
if (uidState <= PROCESS_STATE_TOP) {
ret = getReasonCodeFromProcState(uidState);
}
}
// 有可见的Activity或window
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;
}
}
// 对应上面4, 仅在PendingIntentRecord中才有可能为true
if (ret == REASON_DENIED) {
// Is the allow activity background start flag on?
if (allowBackgroundActivityStarts) {
ret = REASON_START_ACTIVITY_FLAG;
}
}
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;
}
// 对应上面1
if (isCallerSystem) {
ret = REASON_SYSTEM_UID;
}
}
// 涉及到上面4
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;
}
}
if (ret == REASON_DENIED) {
if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {
return REASON_TEMP_ALLOWED_WHILE_IN_USE;
}
}
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;
}
}
}
// 对应上面7
if (ret == REASON_DENIED) {
if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
== PERMISSION_GRANTED) {
ret = REASON_BACKGROUND_ACTIVITY_PERMISSION;
}
}
if (ret == REASON_DENIED) {
final boolean isAllowedPackage =
mAllowListWhileInUsePermissionInFgs.contains(callingPackage);
if (isAllowedPackage) {
ret = REASON_ALLOWLISTED_PACKAGE;
}
}
// 对应上面5
if (ret == REASON_DENIED) {
// Is the calling UID a device owner app?
final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid);
if (isDeviceOwner) {
ret = REASON_DEVICE_OWNER;
}
}
return ret;
}
来自不同的处于可见app发送的PendingingIntent启动的Service,有效期为10s
4.The service is started as a PendingIntent that is sent from a different, visible app
https://developer.android.com/guide/components/foreground-services#background-start-restrictions
android 12+ app不能在后台启动前台Service,否则系统会抛出ForegroundServiceStartNotAllowedException. 但是如果一个app启动另一个app的FGS,这个限制只有在两个app的target sdk同时为android 12+时才生效。
检查应用是否有后台启动:
adb shell device_config put activity_manager \
default_fgs_starts_restriction_notification_enabled true
如果App有后台启动前台Service的行为,建议App更换使用WorkManager来代替FGS。
// Apps that are TOP or effectively similar may call startForeground() on
// their services even if they are restricted from doing that while in bg.
if (!ignoreForeground
&& !appIsTopLocked(r.appInfo.uid)
&& appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
Slog.w(TAG,
"Service.startForeground() not allowed due to bg restriction: service "
+ r.shortInstanceName);
// Back off of any foreground expectations around this service, since we've
// just turned down its fg request.
updateServiceForegroundLocked(psr, false);
ignoreForeground = true;
}
豁免:
Note: When your app is in the frequent bucket or a more restrictive bucket, your high-priority FCM messages might be downgraded to normal priority. If the message’s priority is downgraded, your app can’t start a foreground service. To check the priority of an FCM message that your app receives, call getPriority().
private @ReasonCode int shouldAllowFgsStartForegroundLocked(@ReasonCode int allowWhileInUse,
int callingPid, int callingUid, String callingPackage,
@Nullable ServiceRecord targetService) {
int ret = allowWhileInUse;
// caller在前台
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);
}
}
if (ret == REASON_DENIED) {
final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
if (app.uid == callingUid) {
final ProcessStateRecord state = app.mState;
//TOP之下 BFGS、FGS、BTOP的procState也可以
if (state.isAllowedStartFgsState()) {
return getReasonCodeFromProcState(state.getAllowStartFgsState());
} else {
final ActiveInstrumentation instr = app.getActiveInstrumentation();
if (instr != null
&& instr.mHasBackgroundForegroundServiceStartsPermission) {
return REASON_INSTR_BACKGROUND_FGS_PERMISSION;
}
// caller在5s内可见,对应上面1
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;
}
}
// 此权限不适用于三方App
if (ret == REASON_DENIED) {
if (mAm.checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid,
callingUid) == PERMISSION_GRANTED) {
ret = REASON_BACKGROUND_FGS_PERMISSION;
}
}
// 拥有REASON_SYSTEM_ALERT_WINDOW_PERMISSION 权限,跟2有关系
if (ret == REASON_DENIED) {
if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
callingPackage)) {
ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
}
}
// 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.
// 对应上面的12
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;
}
}
}
//
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;
}
}
}
// 下面两个对应上面的11
if (ret == REASON_DENIED) {
if (UserManager.isDeviceInDemoMode(mAm.mContext)) {
ret = REASON_DEVICE_DEMO_MODE;
}
}
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;
}
}
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;
}
}
// 对应上面的6
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;
}
}
}
//
if (ret == REASON_DENIED) {
if (mAm.mConstants.mFgsAllowOptOut
&& targetService != null
&& targetService.appInfo.hasRequestForegroundServiceExemption()) {
ret = REASON_OPT_OUT_REQUESTED;
}
}
return ret;
}
https://developer.android.com/guide/components/foreground-services#types
android 10+新增location type
android 11+新增camera 和 microphone
<manifest>
...
<service ...
android:foregroundServiceType="location|camera|microphone" />
</manifest>
Notification notification = ...;
Service.startForeground(notification,
FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_CAMERA);
https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running#foreground-service-type
如果app有长时间运行的worker请求location, camera, or microphone,按照上面链接指定worker的类型。
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="location|microphone"
tools:node="merge" />
@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {
// Build a notification...
Notification notification = ...;
return new ForegroundInfo(NOTIFICATION_ID, notification,
FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MICROPHONE);
}