转载请注明出处:https://blog.csdn.net/zwjemperor/article/details/82949913
github:https://github.com/rushgit/zhongwenjun.github.com
概述
一直以来都想把App的启动过程和四大组件的启动过程系统的梳理一 遍,但一直都没有沉下心去阅读源码。因为最近要把一个App的targetSdkVersion升级到26(Android 8.0),需要弄清楚Android 8.0对后台服务究竟做了哪些限制。借此契机把Service的启动流程和后台限制相关的源码梳理了一遍。
在使用Service的过程中,相信不少人有过这些疑问:
- Service的启动流程是怎样的?两种启动方式具体有什么区别?
- Android 8.0+对后台Service究竟做了什么限制?对所有App一视同仁吗?
- 前台服务为什么不受限制?前台服务可以不弹前台通知吗?
- JobService的机制是什么,为什么不受后台限制?
通过阅读本文,了解清楚Service内部机制,你将弄明白这些问题。因涉及到篇幅问题,我将分为两篇文章来介绍Service。
本篇重点介绍:
- 先整体介绍与Service交互过程中涉及到的几个角色,以及它们各自承担的职责;
- 从源码层面讲解Service的启动过程,包括start和bind两种方式,如何启动前台服务,以及后台启动的限制;
- 再从源码层面讲解Service的销毁过程,包括stop和unbind两种方式,以及Service被stop的几种场景、进程状态、ANR机制等;
- 最后进行总结,回答上面提出的几个问题。
每一个小节过后都会梳理出流程图,下一篇将介绍JobService机制。
1. Service整体交互结构
Service作为Android四大组件之一,其生命周期是通过system_server
进程中的ActivityManagerService(AMS)管理的,所以要理解Service的通信机制,首先要了解Binder机制,对Binder不了解的同学请先阅读关于Binder,作为应用开发者你需要知道的全部,本篇不做过多介绍。
下面先上一张图,大致了解Service通信过程中涉及到的几个主要角色。
App端进程:
-
ContextImpl
Context抽象类所有api的实现,是Service、Activity和其他组件base Context。
-
ActivityThread
代表着App的主线程,是App的入口,Application、Activity、Service都在ActivityThread中创建,维护着该App所有运行中的Service实例。其中有一个IApplicationThread类型成员mAppThread,用于被AMS跨进程调用。
-
Service
具体提供服务的Service,被ActivityThread管理。 -
ServiceConnection
监听Service连接状态的接口,用于bindService。
AMS端:
-
ActivityManagerService
四大组件的大管家,是Framework中极为重要的一个类。
-
ActiveServices
AMS中管理Service的具体类。
-
ServiceRecord
Service结构的具体描述。
-
ServiceMap
描述了一个用户(App)的所有Service记录,主要用于检索。
-
ConnectionRecord
Client端与Service端绑定的抽象描述。
2. Service启动过程
2.1 startService
首先看入口:
frameworks/base/core/java/android/app/ContextImpl.java
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier());
......
return cn;
}
ContextImpl中只做了validateServiceIntent
校验(target 21之后限制隐式启动),然后调用了AMS
的startService
方法。再看AMS
中的实现:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
......
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;
}
}
直接调用了ActiveServices
的startServiceLocked
方法。
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 {
......
// 1. 从mServiceMap中查询SerivceRecord缓存,如果没有则创建一个
ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg, false);
......
ServiceRecord r = res.record;
......
// If this isn't a direct-to-foreground start, check our ability to kick off an arbitrary service
// fgRequired为false,即不是启动前台服务
if (!r.startRequested && !fgRequired) {
// 2. 检查是否允许启动方应用启动Service
final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName, r.appInfo.targetSdkVersion, callingPid, false, false);
// app mode不为APP_START_MODE_NORMAL表示应用处于后台,而不在后台不受限的白名单中
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service " + service + " to " + r.name.flattenToShortString() + " from pid=" + callingPid + " uid=" + callingUid + " pkg=" + callingPackage);
// 不允许启动后台Service
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
......
r.startRequested = true;
......
final ServiceMap smap = getServiceMapLocked(r.userId);
boolean addToStarting = false;
// 3. 非前台调用,且非启动前台服务,且app进程未启动
if (!callerFg && !fgRequired && r.app == null && mAm.mUserController.hasStartedUserState(r.userId)) {
ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
// 如果调用方进程不在前台,而且正在启动的后台Service过多,该Service会被延时启动,避免在短时间内启动大量进程。
if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
smap.mDelayedStartList.add(r);
r.delayed = true;
return r.name;
}
addToStarting = true;
}
......
}
......
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
return cmp;
}
这个方法中做了几个检查操作:
- 从
mServiceMap
中查询SerivceRecord
缓存,如果没有则创建一个; - 如果不是启动前台服务,会检查启动方是否能启动Service,如果启动方应用不在前台,且未在允许后台启动Service的白名单中,将禁止启动。(白名单的逻辑在后面介绍)
- 如非前台调用,也非启动前台服务,且app进程未启动,且正在启动的后台Service过多,该Service会被延时启动,避免在短时间内启动大量进程。
- 通过了前面的检查,调用
startServiceInnerLocked
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r, boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
......
// 调用bringUpServiceLocked启动Service
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
......
return r.name;
}
startServiceInnerLocked
调用bringUpSrviceLocked
启动Service。
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired)
throws TransactionTooLargeException {
// 1.如果此Service已经被启动,直接调用onStartCommand
if (r.app != null && r.app.thread != null) {
sendServiceArgsLocked(r, execInFg, false);
return null;
}
......
if (!isolated) {
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
// 2.Service所属进程已经启动
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
// 进入真正启动Service流程
realStartServiceLocked(r, app, execInFg);
return null;
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
}
}
// 3.如果Service所属进程尚未启动,则先启动进程
if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, hostingType, r.name, false, isolated, false)) == null) {
bringDownServiceLocked(r);
return msg;
}
}
// 加入Pengding列表
if (!mPendingServices.contains(r)) {
mPendingServices.add(r);
}
......
return null;
}
这个方法做了3件事情:
- 如果此Service已经被启动,直接调用
onStartCommand
; - 如果此Service未启动,但所属进程已启动,则调用
realStartServiceLocked
进入真正启动Service的流程; - 如果Service所属进程尚未启动,则先启动进程,如app进程启动失败则销毁此Service;如启动成功,则加入Pengding启动列表,待App进程启动结束后再启动Service。
下面看真正启动Service的方法realStartServiceLocked
:
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
.....
final boolean newService = app.services.add(r);
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
// 1.调整应用进程优先级
mAm.updateOomAdjLocked();
boolean created = false;
try {
......
mAm.notifyPackageUse(r.serviceInfo.packageName, PackageManager.NOTIFY_PACKAGE_USE_SERVICE); app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
// 2.通知ActivityThread创建Service,调用onCreate
app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);
// 3.如果已经设置通知,创建前台通知
r.postNotification();
created = true;
}
......
// 通知ActivityThread调用Service的onBind方法
requestServiceBindingsLocked(r, execInFg);
updateServiceClientActivitiesLocked(app, null, true);
// 1.如启动前台服务,则发送一个5s的延时消息,如5s内未调用Service.startForeground,应用将ANR
// 2.通知ActivityThread调用Service的onStartCommand方法
sendServiceArgsLocked(r, execInFg, true);
......
}
-
通知
AMS
调整应用进程优先级 -
跨进程调用,通过Service所属进程的
IApplicationThread
,即ActivityThread创建Service实例,再调用其onCreate方法; -
如果已经设置通知,则创建前台通知;
-
如果Service已经被绑定,则调用
onBind
方法; -
调用
sendServiceArgsLocked
这个方法主要做了2个事情:
1)如启动前台服务,则发送一个5s的延时消息,如5s内未调用
Service.startForeground
,应用将ANR;2)通知ActivityThread调用Service的
onStartCommand
方法;
大家对第3、4步可能会有一个疑问:Service刚创建,肯定没有调用
startForeground
设置前台通知,也没有被bind,为什么要检查是否创建前台通知、调用onBind呢?
在前一个方法bringUpServiceLocked
中我们已经介绍,当应用进程未启动时,AMS
首先是去启动Service所属进程,同时ServiceRecord
放进了Pengding列表。在应用主线线被创建后,再启动之前被挂起的Service,所以是存在已经设置前台通知或者被bind的情况的。
本篇重点介绍Service的启动流程,Pengding Service被启动的逻辑不做详细介绍,调用过程如下,有兴趣的同学可以查阅源码。
- ActivityThread.attach
- AMS.attachApplication
- AMS.attachApplicationLocked
- ActiveServices.attachApplicationLocked
- ActiveServices.realStartServiceLocked
流程图:start
2.2 bindService
下面看bindService
的流程,入口同样在ContextImpl
中。
frameworks/base/core/java/android/app/ContextImpl.java
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
warnIfCallingFromSystemProcess();
return