在官方文档 Android 8.0 行为变更 中有这样一段话:

Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService() ,以在前台启动新服务。

在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。

如果应用在此时间限制内 调用 startForeground() ,则系统将停止服务并声明此应用为 ANR

Android service 启动篇之 startService 中对整个start 过程进行了梳理,其中startService 和startForegroundService 最终调用调用的接口时一样的,只是其中要求foreground 启动service。基于上一篇博文,这里对于前台服务进行详细的解析。

1 startServiceLocked

流程同 Android service 启动篇之 startService ,最终调用接口为ActivieServices 中startServiceLocked:

        r.lastActivity = SystemClock.uptimeMillis();
        r.startRequested = true;
        r.delayedStop = false;
        r.fgRequired = fgRequired;
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                service, neededGrants, callingUid));

初始化ServiceRecord,其中fgRequired 为true。

然后将需要start 的service 添加到pendingStarts 中, Android service 启动篇之 startService 中知道最后会在bringUpServiceLocked的函数中进行最终启动。

对于前台服务 sendServiceArgsLocked() 函数中会拉起一个timeout,时长为 5 秒,也就是说5s 后会抛出ANR的异常。

详细看下面 第 4 点

从这里我们知道在Context.startForegroundService() 之后必须要调用Service.startForeground,也就是说在foreground 的启动接口调用后的 5 秒内必须要在service 中调用startForeground() 接口来解除timeout。

2 startFroeground

来看下是否是这样设计的,来看下startFroeground():

    public final void startForeground(int id, Notification notification) {
        try {
            mActivityManager.setServiceForeground(
                    new ComponentName(this, mClassName), mToken, id,
                    notification, 0);
        } catch (RemoteException ex) {

在函数的上面有段注释:

     * @param id The identifier for this notification as per
     * {@link NotificationManager#notify(int, Notification)
     * NotificationManager.notify(int, Notification)}; must not be 0.
     * @param notification The Notification to be displayed.
     * @see #stopForeground(boolean)

一共 5 个参数,其中id 和notification 是需要通过service 传入的。id 是用于notification notify 使用。

3 setServiceForegroundInnerLocked

3.1 取消timeout

接着来看AMS 中的接口,最终调用的是ActiveServices 中的setServiceForegroundInnerLocked():

            if (r.fgRequired) {
                if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
                    Slog.i(TAG, "Service called startForeground() as required: " + r);
                r.fgRequired = false;
                r.fgWaiting = false;
                mAm.mHandler.removeMessages(
                        ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);

fgRequired 在这里会被置成false,意味了这个请求已经被安全处理。

这里看到会取消掉foreground 的timeout,但是,前提条件是:

        if (id != 0) {
            if (notification == null) {
                throw new IllegalArgumentException("null notification");

要求startFroeground() 中的id 不能为0,而且notification不能为null。

上面提到sendServiceArgsLocked() 的时候会schedule 一个timeout,时长为5秒,5秒过了之后会出现ANR。那需要注意的是函数sendServiceArgsLocked() 是在onCreate() 之后,并且是在onStartCommand() 之前调用的,这就给了我们取消的空间。虽然说都是异步的操作,但是为了正常流程考虑,一般会将startFroeground() 加到onStartCommand() 中执行。

3.2 隐藏notification

            if (r.foregroundId != id) {
                cancelForegroundNotificationLocked(r);
                r.foregroundId = id;

code 中在foreground 的id 发生变化的时候,会将原来的notification 隐藏掉。

那有种可能,有可能两个service 公用一个notification,这个时候不需要将notification cancel。

    private void cancelForegroundNotificationLocked(ServiceRecord r) {
        if (r.foregroundId != 0) {
            // First check to see if this app has any other active foreground services
            // with the same notification ID.  If so, we shouldn't actually cancel it,
            // because that would wipe away the notification that still needs to be shown
            // due the other service.
            ServiceMap sm = getServiceMapLocked(r.userId);
            if (sm != null) {
                for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
                    ServiceRecord other = sm.mServicesByName.valueAt(i);
                    if (other != r && other.foregroundId == r.foregroundId
                            && other.packageName.equals(r.packageName)) {
                        // Found one!  Abort the cancel.
                        return;
            r.cancelNotification();

3.3 将service 设为前台服务

    if (!r.isForeground) {
        final ServiceMap smap = getServiceMapLocked(r.userId);
        if (smap != null) {
            ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName);
            if (active == null) {
                active = new ActiveForegroundApp();
                active.mPackageName = r.packageName;
                active.mUid = r.appInfo.uid;
                active.mShownWhileScreenOn = mScreenOn;
                if (r.app != null) {
                    active.mAppOnTop = active.mShownWhileTop =
                            r.app.uidRecord.curProcState
                                    <= ActivityManager.PROCESS_STATE_TOP;
                active.mStartTime = active.mStartVisibleTime
                        = SystemClock.elapsedRealtime();
                smap.mActiveForegroundApps.put(r.packageName, active);
                requestUpdateActiveForegroundAppsLocked(smap, 0);
            active.mNumActive++;
        r.isForeground = true;

4 异常处理

4.1 ANR

上面已经说过,如果在 5 秒内没有调用startForeground(),timeout 就会触发,会报出ANR:

    void serviceForegroundTimeout(ServiceRecord r) {
        ProcessRecord app;
        synchronized (mAm) {
            if (!r.fgRequired || r.destroying) {
                return;
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "Service foreground-required timeout for " + r);
            app = r.app;
            r.fgWaiting = false;
            stopServiceLocked(r);
        if (app != null) {
            mAm.mAppErrors.appNotResponding(app, null, null, false,
                    "Context.startForegroundService() did not then call Service.startForeground()");

log 如下:

11-06 02:01:59.616  3877  3893 E ActivityManager: ANR in com.shift.phonemanager.permission.accesslog
11-06 02:01:59.616  3877  3893 E ActivityManager: PID: 1369
11-06 02:01:59.616  3877  3893 E ActivityManager: Reason: Context.startForegroundService() did not then call Service.startForeground()
11-06 02:01:59.616  3877  3893 E ActivityManager: Load: 0.0 / 0.0 / 0.0
11-06 02:01:59.616  3877  3893 E ActivityManager: CPU usage from 7945ms to 0ms ago (2007-11-06 02:01:51.418 to 2007-11-06 02:01:59.363):
11-06 02:01:59.616  3877  3893 E ActivityManager:   60% 3877/system_server: 35% user + 25% kernel / faults: 3744 minor 6 major
11-06 02:01:59.616  3877  3893 E ActivityManager:   25% 1042/com.android.launcher3: 20% user + 4.9% kernel / faults: 11190 minor 9 major
11-06 02:01:59.616  3877  3893 E ActivityManager:   18% 1149/android.process.media: 13% user + 5.3% kernel / faults: 6130 minor
11-06 02:01:59.616  3877  3893 E ActivityManager:   15% 1420/adbd: 3.6% user + 11% kernel / faults: 5074 minor
11-06 02:01:59.616  3877  3893 E ActivityManager:   9.7% 255/logd: 2.7% user + 6.9% kernel / faults: 5 minor
11-06 02:01:59.616  3877  3893 E ActivityManager:   9.2% 3814/surfaceflinger: 4.4% user + 4.8% kernel / faults: 658 minor

4.2 crash

上面看到如果timeout 触发,会报出ANR,但是code 中也有另外一个地方限制,要求service 一旦startForegroundService() 启动,必须要在service 中startForeground(),如果在这之前stop 或stopSelf,那就会用crash 来代替ANR。

详细看bringDownServiceLocked()。

        if (r.fgRequired) {
            Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                    + r);
            r.fgRequired = false;
            r.fgWaiting = false;
            mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
            if (r.app != null) {
                Message msg = mAm.mHandler.obtainMessage(
                        ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
                msg.obj = r.app;
                mAm.mHandler.sendMessage(msg);

这里的r.fgRequired 必须要处理掉,不然stop 的时候会触发bringDown,然后会将timeout 的remove,换成了crash。

log 如下:

--------- beginning of crash
11-06 02:06:05.307  3106  3106 E AndroidRuntime: FATAL EXCEPTION: main
11-06 02:06:05.307  3106  3106 E AndroidRuntime: Process: com.shift.phonemanager.permission.accesslog, PID: 3106
11-06 02:06:05.307  3106  3106 E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1771)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:106)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:164)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6518)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
11-06 02:06:05.320  3118  3118 D ExtensionsFactory: No custom extensions.
  • 8.0 以后不希望后台应用运行后台服务,除非特殊条件
  • 一旦通过startForegroundService() 启动前台服务,必须在service 中有startForeground() 配套,不然会出现ANR 或者crash
  • startForeground() 中的id 和notification 不能为0 和 null
前言:在官方文档Android 8.0 行为变更中有这样一段话:Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即Context.startForegroundService(),以在前台启动新服务。在系统创建服务后,应用有五秒的时间来调用该服务的startForeground()方法以显示新服务的...
本文主要分析Android O startForegroundService(前台服务)的流程,以及出现Context.startForegroundService() did not then call Service.startForeground()和null notification 的原因。 startForegroundService使用方式:https://blog.csdn.ne...
一、如何保活后台服务 在Android Services (后台服务)里面,我们了解了Android四大组件之一的Service,知道如何使用后台服务进行来完成一些特定的任务。但是后台服务在系统内存不足的时候,可能会被系统杀死。那么如何让后台服务尽量不被杀死呢?基本的解决思路主要有以下几种: 1. 提高Service的优先级: <!-- 为防止Service被系统回收,可以尝试通过提高服务的优先级解决,1000是最高优先级,数字越小,优先级越低 --> android:priori.
startForegroundServiceAndroid 8.0里 ,应用在后台的时候调用了Context.startService 此时会触发: java.lang.IllegalStateException: Not allowed to start service Intent 我们需要改成: Context.startForegroundService() 并且在Context.startForegroundService() 之后必须要调用Service.startForeground
解决context.startforegroundservice() did not then call service.startforeground() Android 8.0 系统不允许后台应用创建后台服务,故只能使用Context.startForegroundService()启动服务 创建服务后,应用必须在5秒内调用该服务的startForeground()显示一条可见通知,声明有服务在挂着,不然系统会停止服务 + ANR 套餐送上。 Notification 要加 Chan..
class SocketService : Service() { private var webSockethandler: WebSocketHandler? = null private var pushObserve: Observer<String>? = null private var taskLog: Int = 0 private va.
前言:之前梳理了startService和bindService,现在接着梳理下Android O比较有特点的startForegroundService。 (六十四)Android O Service启动流程梳理——startService (六十五)Android O StartService的 anr timeout 流程分析 (七十)Android O Service启动流程梳理——...
12-13 10:41:07.520 16661 16661 E AndroidRuntime: FATAL EXCEPTION: main 12-13 10:41:07.520 16661 16661 E AndroidRuntime: Process: cn.xxx.xxxxx:remote, PID: 16661 12-13 10:... Android Service启动方式有两种: 1. startService()方法:通过调用该方法启动ServiceService会一直运行直到被stopService()或stopSelf()方法停止。 2. bindService()方法:通过调用该方法绑定ServiceService会一直运行直到所有绑定的客户端都解除绑定,然后Service会被销毁。 ### 回答2: Android ServiceAndroid 中一种特殊的组件,它可以在后台运行任务,而不影响用户交互。Android Service 启动方式有三种:startService、 bindService、 和 startForegroundService。 1. StartService 启动方式 StartService 是最基本的启动方式,它使用 Intent 启动 Service。通过调用 startService() 方法,系统会在后台启动一个 Service,并将它添加进 Service 栈中。接下来 Service 会在后台一直运行直至被停止停止(调用 stopService() 方法)。StartService 启动方式可以用于处理一些不需要与 Activity 交互的后台任务。 2. BindService 启动方式 与 StartService 不同,BindService 启动方式是用于与 Service 进行交互的。它使用 bindService() 方法,绑定一个 Service 实例到指定的 Activity 上下文。Service 绑定后,它和 Activity 就可以进行通信,通过 ServiceConnection 接口回调方法进行数据交换和通信。 3. StartForegroundService 启动方式 在 Android 8.0(API level 26)及以上版本,应用不能后台无限制运行,系统会对后台运行的应用进行限制。StartForegroundService 启动方式是用于在前台启动 Service 并让 Service 继续在后台运行,避免被系统杀掉。通常用于需要进行长时间任务处理的 Service,或者需要向用户展示的服务。对于 StartForegroundService 启动方式,使用 startForeground() 方法在方法中接收一个 id 和 Notification 对象参数,用于展示服务运行的状态。 三种启动 Service 的方式都各有用途,应根据需求进行选择。StartService 启动方式非常适用于不需要与 Activity 交互的后台任务;BindService 启动方式适用于 Activity 和 Service 之间进行交互和通信;StartForegroundService 启动方式则适用于需要长时间运行任务或向用户展示服务状态的场景。 ### 回答3: Android Service是一种在后台执行长时间运行任务的应用程序组件。它通常没有用户界面,可以在不影响前台活动的情况下继续运行。该服务可以独立运行,也可以与其他应用程序组件(如活动和广播接收器)一起使用。在Android中,启动Service有三种方式: 1. Context.startService()方法 使用该方法,可以启动一个Service,并在后台运行它。它不会返回结果,但可以与Service进行通信,以便进行更改或停止Service。这种方式适用于启动一些后台任务,例如在手机锁定后继续运行下载任务。 2. Context.bindService()方法 通过该方法,可以将客户端与Service绑定。该方法返回一个IBinder对象,该对象允许客户端以编程方式调用Service方法并获得结果。这种方式适用于需要在应用程序间进行数据交换的情况,例如通过Service来获得其他应用程序的数据。 3. 通过Manifest文件声明的方式 可以在应用程序的Manifest文件中声明Service,并指定其当应用程序启动时应自动启动。这种方式适用于需要在应用程序启动时或设备启动启动Service的情况,例如在设备启动启动音乐播放器的Service。 以上是几种常见的Android Service启动方式,可以根据应用程序的需求选择合适的方式进行启动