前台Service发展历史

启动和移除

启动

通常在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值参数表明是否移除与该前台服务有关的通知。
如果在前台服务运行时停止该服务,则与之相关的通知会被移除。

通知

android 12+通知延迟10s显示

https://developer.android.com/guide/components/foreground-services#notification-immediate
android 12+系统会等待10s才显示跟前台Service有关的通知(为短期运行的FGS提供优化体验),以下情况可以豁免(即立刻显示通知):

  1. The service is associated with a notification that includes action buttons.
  2. The service has a foregroundServiceType of mediaPlayback, mediaProjection, or phoneCall.
  3. The service provides a use case related to phone calls, navigation, or media playback, as defined in the notification’s category attribute.
  4. Service在配置通知时调用setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
 // 对应上面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的通知
在这里插入图片描述

android 13+通知运行时权限

https://developer.android.com/about/versions/13/changes/notification-permission
Android 13 中引入了新的运行时权限,用于从应用发送非豁免通知:POST_NOTIFICATIONS。此更改有助于用户专注于最重要的通知。

权限

android 9+前台Service普通权限

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>

android 13+通知运行时权限

https://developer.android.com/about/versions/13/changes/notification-permission

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <application ...>
        ...
    </application>
</manifest>

限制

android 11+ while-in-use权限

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

豁免:

  1. The service is started by a system component.
  2. The service is started by interacting with app widgets.
  3. The service is started by interacting with a notification.
  4. The service is started as a PendingIntent that is sent from a different, visible app.
  5. The service is started by an app that is a device policy controller that is running in device owner mode.
  6. The service is started by an app which provides the VoiceInteractionService.
  7. The service is started by an app that has the START_ACTIVITIES_FROM_BACKGROUND privileged permission.

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
在这里插入图片描述

android 12+后台启动限制

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;
                }

豁免:

  1. Your app transitions from a user-visible state, such as an activity.
  2. Your app can start an activity from the background, except for the case where the app has an activity in the back stack of an existing task.
  3. Your app receives a high-priority message using Firebase Cloud Messaging.

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().

  1. The user performs an action on a UI element related to your app. For example, they might interact with a bubble, notification, widget, or activity.
  2. Your app invokes an exact alarm to complete an action that the user requests.
  3. Your app is the device’s current input method.
  4. Your app receives an event that’s related to geofencing or activity recognition transition.
  5. After the device reboots and receives the ACTION_BOOT_COMPLETED, ACTION_LOCKED_BOOT_COMPLETED, or ACTION_MY_PACKAGE_REPLACED intent action in a broadcast receiver.
  6. Your app receives the ACTION_TIMEZONE_CHANGED, ACTION_TIME_CHANGED, or ACTION_LOCALE_CHANGED intent action in a broadcast receiver.
  7. Your app receives a Bluetooth broadcast that requires the BLUETOOTH_CONNECT or BLUETOOTH_SCAN permissions.
  8. Apps with certain system roles or permission, such as device owners and profile owners.
  9. Your app uses the Companion Device Manager and declares the REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND permission or the REQUEST_COMPANION_RUN_IN_BACKGROUND permission. Whenever possible, use REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND.
  10. The user turns off battery optimizations for your app. You can help users find this option by sending them to your app’s App info page in system settings. To do so, invoke an intent that contains the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent action.
    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;
    }

声明前台Service类型

https://developer.android.com/guide/components/foreground-services#types

声明Service类型

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);

声明WorkerManager类型

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);
}

猜你喜欢

转载自blog.csdn.net/xiaoyantan/article/details/124528537