前言:

在官方文档 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.

5 总结

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

转Android service 启动篇之 startForegroundService的更多相关文章

  1. Android - Service启动机制

      以下资料摘录整理自老罗的Android之旅博客,是对老罗的博客关于Android底层原理的一个抽象的知识概括总结(如有错误欢迎指出)(侵删):http://blog.csdn.net/luoshe ...

  2. Android Service 启动和停止服务

    activity_main.xml 定义两个Button控件,start_service和stop_service. <LinearLayout xmlns:android="http ...

  3. Android Service 启动流程

    执行顺序 : startService -> bindService -> unbindService -> stopService 回调的结果为: 执行顺序 : startServ ...

  4. Android总结篇系列:Android Service

    Service通常总是称之为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: ...

  5. Android启动篇 — init原理(二)

    ========================================================          ================================== ...

  6. Android启动篇 — init原理(一)

    ========================================================          ================================== ...

  7. Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  8. Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  9. Android核心服务解析篇(三)——Android系统的启动

    从大的方面来说.Android系统的启动能够分为两个部分:第一部分是Linux核心的启动,第二部分是Android系统的启动. 第一部分主要包含系统引导,核心和驱动程序等,因为它们不属于本篇要讲的内容 ...

随机推荐

  1. Apache Solr应用服务器存在远程代码执行漏洞👻

    Apache Solr应用服务器存在远程代码执行漏洞 1.描述 Apache Solr是一个开源的搜索服务,使用Java语言开发,主要基于HTTP和Apache Lucene实现的. Solr是一个高 ...

  2. 设计模式二--模板方法Template method

    模式分类: 书籍推荐:重构-改善既有代码的设计 重构获得模式 设计模式:现代软件设计的特征是"需求的频繁变化".设计模式的要点是 "寻找变化点,然后在变化点处应用设计模式 ...

  3. js 实现匀速移动

    js 实现匀速移动 <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  4. k8s之mutating webhook + gin

    1.知识准备 1.Webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制 2.Webhook 接收来自apiserver的回调,对回调资源做一些校验.注入.修改元数据等工作 3.来 ...

  5. Swift学习笔记(一)

    1.Constants and Variables(常量和变量) let定义常量,var定义变量. [Note] If a stored value in your code won't change ...

  6. 跟着老猫来搞GO-容器(1)

    前期回顾 前面的一章主要和大家分享了GO语言的函数的定义,以及GO语言中的指针的简单用法,那么本章,老猫就和大家一起来学习一下GO语言中的容器. 数组 数组的定义 说到容器,大家有编程经验的肯定第一个 ...

  7. requests的get请求基本使用

    官方文档 https://docs.python-requests.org/zh_CN/latest/   快速上手 https://docs.python-requests.org/zh_CN/la ...

  8. Android学习—下载Android SDK的两种方式

    在Android Studio中下载Android SDK的两种方式 Android studio下载地址:http://www.android-studio.org/ 方式一.设置HTTP Prox ...

  9. [bzoj1576]安全路径

    先建立最短路径树(即跑dij每一个点向更新他的点连边),考虑一个点的答案路径一定要走过且仅走过一条非树边,枚举非树边(x,y),对于一个点k,如果它在x~lca上(y~lca的路径上同理),那么答案可 ...

  10. [loj519]数学上来先打表

    建立操作树,即1和3操作时i-1向i连边,2操作中k向i连边,然后dfs一遍 那么当我们走到一个节点,就执行该操作(修改也是操作),退出后取消该操作即可 于是相当于要维护一个东西,支持:1.加边:2. ...