Android service 启动篇之 startForegroundService

系列博文:

Android 中service 详解

Android service 启动篇之 startService

Android service 启动篇之 bindService

Android service 启动篇之 startForegroundService

 

 

基于版本:android O

前言:

在官方文档 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) {}}

在函数的上面有段注释:

            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.

5 总结

  • 8.0 以后不希望后台应用运行后台服务,除非特殊条件
  • 一旦通过startForegroundService() 启动前台服务,必须在service 中有startForeground() 配套,不然会出现ANR 或者crash
  • startForeground() 中的id 和notification 不能为0 和 null


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部