一 前言
这节的内容也是由于项目中的一个Bug引起:
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:41:07.520 16661 16661 E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{ed71832 u0 cn.xxx.xxxxx/com.amap.api.location.APSService}
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1879)
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at android.os.Looper.loop(Looper.java:217)
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7356)
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:499)
12-13 10:41:07.520 16661 16661 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:865)
Android O 之前,创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。Android O及Android P,系统不允许后台应用创建后台服务,Android O 引入了一种全新的方法,即ContextCompat.startForegroundService()
,以在前台启动新服务。
启动一个前台服务要做的事情有:
(1) 调用 ContextCompat.startForegroundService()
可以创建一个前台服务,相当于创建一个后台服务并将它推到前台;
(2) 创建一个用户可见的 Notification
;
(3) 必须在5秒内调用该服务的 startForeground(int id, Notification notification)
方法,否则将停止服务并抛出 android.app.RemoteServiceException:Context.startForegroundService() did not then call Service.startForeground()
异常。
什么是前台应用
满足以下任意条件的应用被视为处于前台:
具有可见的 Activity;
具有前台服务;
另一个前台应用关联到该应用,如输入法、壁纸服务、语音服务等。
如果以上条件均不满足,应用将被视为处于后台。
前台服务后台服务的区别
前台服务会在通知一栏显示ONGOING的Notification,当服务被终止的时候,通知一栏的Notification也会消失,这样对于用户有一定的通知作用,常见的如音乐播放服务。
默认的服务即为后台服务,即不会在通知一栏显示ONGOING的Notification。当服务被终止的时候,用户是看不到效果的,某些不需要运行或终止提示的服务,如天气更新、日期同步、邮件同步等。
后台服务我们可以自己创建 ONGOING 的 Notification 这样就成为前台服务吗?答案是否定的,前台服务在做了上述工作之后需要调用 startForeground ( android 2.0 及其以后版本 )或 setForeground (android 2.0 以前的版本)使服务成为前台服务。这样的话,当服务被外部强制终止掉的时候,ONGOING 的 Notification 也会移除掉。
二 图示调用流程
主要调用流程和StartService的流程基本相似。
三 具体代码流程
第一部分
启动前台服务调用者调用startForegroundService,第一部分主要随着调用流程的分析我们看看为什么会出现Context.startForegroundService() did not then call Service.startForeground()异常。
1 frameworks/base/core/java/android/content/ContextWrapper.java
@Override
public ComponentName startForegroundService(Intent service) {
return mBase.startForegroundService(service);
}
这里mBase变量是ContextImpl类型,是在创建activity的时候,new 一个ContextImpl对象,赋值给activity的。
2 frameworks/base/core/java/android/app/ContextImpl.java
@Override
public ComponentName startForegroundService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, true, mUser);
}
startServiceCommon
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
和startService的流程相似,还是走到AMS的startService,requireForeground为true。可以先回看startService的流程Android组件管理框架—后台服务Service之startService方式启动流程(Android P)。
3 frameworks/base/core/java/android/app/ActivityManager.java
4 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
调用到系统进程(System_server)来了。
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
return res;
}
}
接着看mServices.startServiceLocked,mServices是ActiveService的实例。
5 frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
final boolean callerFg;
if (caller