转Android service 启动篇之 startForegroundService
前言:
在官方文档 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的更多相关文章
- Android - Service启动机制
以下资料摘录整理自老罗的Android之旅博客,是对老罗的博客关于Android底层原理的一个抽象的知识概括总结(如有错误欢迎指出)(侵删):http://blog.csdn.net/luoshe ...
- Android Service 启动和停止服务
activity_main.xml 定义两个Button控件,start_service和stop_service. <LinearLayout xmlns:android="http ...
- Android Service 启动流程
执行顺序 : startService -> bindService -> unbindService -> stopService 回调的结果为: 执行顺序 : startServ ...
- Android总结篇系列:Android Service
Service通常总是称之为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: ...
- Android启动篇 — init原理(二)
======================================================== ================================== ...
- Android启动篇 — init原理(一)
======================================================== ================================== ...
- Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)
======================================================== ================================== ...
- Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)
======================================================== ================================== ...
- Android核心服务解析篇(三)——Android系统的启动
从大的方面来说.Android系统的启动能够分为两个部分:第一部分是Linux核心的启动,第二部分是Android系统的启动. 第一部分主要包含系统引导,核心和驱动程序等,因为它们不属于本篇要讲的内容 ...
随机推荐
- cloudstack部署
参考文档 https://blog.csdn.net/u012124304/article/details/80960504#Mysql_37 cloudstack的rpm包下载地址 http://d ...
- python生成有声小说模拟真人发音
生成有声小说原理 文字是1500字内的生成微软文档说说 用代码实现小说爬取正本 实现每章小说1450字 实现自动剪切后添加封面 实现自动上传 用python代码实现爬取小说,本案列以一本小说为实列代码 ...
- k8s入坑之路(12)ingress-nginx安装配置四层代理
ingress官方文档地址:http://docs.kubernetes.org.cn/ https://feisky.gitbooks.io/kubernetes/content/plugins/ ...
- mongodb安装教程(一)
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/fengtingYan/article/de ...
- Java经典面试题-不古出品
@ 目录 一.Java 基础 1.JDK 和 JRE 有什么区别? 2.== 和 equals 的区别是什么? 3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? ...
- 12组-Alpha冲刺-4/6
侯钦凯 过去两天完成了哪些任务 完善UI界面,复习考试 展示GitHub当日代码/文档签入记录 接下来的计划 复习考试,准备答辩 还剩下哪些任务 博客和答辩 燃尽图(团队整体) 遇到了哪些困难 在部分 ...
- logstash插件配置-codec插件说明json和multiline
编码插件(Codec) Codec 是 logstash 从 1.3.0 版开始新引入的概念(Codec 来自 Coder/decoder 两个单词的首字母缩写). 在此之前,logstash 只支持 ...
- [hdu7022]Jsljgame
先考虑$x=y$的情况,此时即是一个平等博弈,因此考虑$sg$函数 具体的,有$sg(n)=\begin{cases}0&(n=0)\\mex(\{sg(n-i)\mid 1\le i\le ...
- [bzoj1264]基因匹配
首先朴素dp的方程,即$f[i][j]=max(f[i][j-1],f[i-1][j],(a[i]==b[j])*(f[i-1][j-1]+1))$,这中间特殊的转移只在a[i]=b[j]时,而在这道 ...
- [bzoj1177]Oil
考虑将三个矩形按某种方式划分为再三个大矩形中找最大值,容易发现只有6种划分方式,分为两类:1.4种,考虑第一条横/竖和第二条在第一条的两侧,这一类情况只需要预处理出左上/左下/右上/右下的最大子矩阵即 ...