在官方文档 Android 8.0 行为变动 中有这样一段话:html
Android 8.0 有一项复杂功能;系统不容许后台应用建立后台服务。 所以,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService()
,以在前台启动新服务。java
在系统建立服务后,应用有五秒的时间来调用该服务的 startForeground()
方法以显示新服务的用户可见通知。android
若是应用在此时间限制内未调用 startForeground()
,则系统将中止服务并声明此应用为 ANR。安全
Android service 启动篇之 startService 中对整个start 过程进行了梳理,其中startService 和startForegroundService 最终调用调用的接口时同样的,只是其中要求foreground 启动service。基于上一篇博文,这里对于前台服务进行详细的解析。app
流程同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。ide
而后将须要start 的service 添加到pendingStarts 中,Android service 启动篇之 startService 中知道最后会在bringUpServiceLocked的函数中进行最终启动。函数
对于前台服务 sendServiceArgsLocked() 函数中会拉起一个timeout,时长为 5 秒,也就是说5s 后会抛出ANR的异常。oop
详细看下面第 4 点。ui
从这里咱们知道在Context.startForegroundService() 以后必需要调用Service.startForeground,也就是说在foreground 的启动接口调用后的 5 秒内必需要在service 中调用startForeground() 接口来解除timeout。
来看下是不是这样设计的,来看下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 使用。
接着来看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() 中执行。
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(); } }
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; }
上面已经说过,若是在 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
上面看到若是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.