本文讲解一下Android 四大组件之一的Service组件,与Activity组件不同,Service组件一般运行在后台,不直接与用户交互,用来放在后台执行长期任务的。例如我们播放器中退出前台,还可以继续播放音乐,还有下载app中退出前台,还可以继续下载,也是用的Service服务,这就需要我们掌握Service的基本用法,还要深入了解Service为什么是这么工作的,这种“知其所以,知其所以然”的品质是我们学习中应该要坚持的。话不多说,开始本文讲解。
- Service使用
- Service启动
- Service超时
- Service注意点
二、Service启动
Service的启动方式有两种,startService和bindService,这和Activity的启动有点像,但是Service启动不像Activity启动那么复杂,我们深入学习一下这两种启动方式内部的运行机制。
2.1 Service结构拆解
Service结构图.jpgService相关的类图,最终都是继承Context这个抽象类的,和Activity一样的。
- Service都有Context所有的通用方法,可以通过当前的context获取应用的一些基本信息:Activity信息和package信息等等。
- ComponentCallbacks2接口以及其父接口中有两个方法:
1.void onConfigurationChanged(Configuration newConfig);
当设备配置发生改变的时候,会回调这个接口,例如屏幕旋转等等。
2.void onLowMemory();
当内存较低的时候会回调这个函数,至于内存较低的标准。
核心的判断在AMS中的doLowMemReportIfNeededLocked(...)函数中,代码如下:
if ((rec.lastLowMemory+mConstants.GC_MIN_INTERVAL) <= now) {
// The low memory report is overriding any current
// state for a GC request. Make sure to do
// heavy/important/visible/foreground processes first.
if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
rec.lastRequestedGc = 0;
} else {
rec.lastRequestedGc = rec.lastLowMemory;
}
rec.reportLowMemory = true;
rec.lastLowMemory = now;
mProcessesToGc.remove(rec);
addProcessToGcListLocked(rec);
}
当前的 rec.reportLowMemory = true 执行之后,会在check内存的时候跨跨进程调用ActivityThread中函数,最终回调到onLowMemory()这个函数,在进程管理的时候会详细讲解这个过程。本次一笔带过。
Service中的主要方法汇总
Service本身就是一个抽象类,需要它的子类实现它的一些方法。
函数方法 | 参数介绍 | 函数作用 |
---|---|---|
onCreate | 没有参数 | service被创建的时候首次调用的方法,不需要开发者主动调用 |
onStart | (Intent intent, int startId) | serivce启动流程的一个阶段,但是只有startService启动的时候会执行到这儿 |
onStartCommand | (Intent intent, @StartArgFlags int flags, int startId) | 当使用startService启动service的时候系统会调用这个方法 |
onDestroy | 没有参数 | service被销毁的流程 |
onConfigurationChanged | (Configuration newConfig) | 设备设置改变的时候回调这个方法 |
onLowMemory | 没有参数 | 低内存函数回调 |
onTrimMemory | (int level) | 低内存等级check |
onBind | (Intent intent) | 当调用bindService的时候会执行到这个流程,表示当前正在执行bind流程,这时候此方法一定要被继承实现 |
onUnbind | (Intent intent) | 解绑service的时候回调这个方法 |
onRebind | (Intent intent) | 新的客户端要连接到这个service,但是这个service之前被解绑了,本次激活需要重新连接 |
介绍完Service中常用的一些方法,我们就要分析Service的启动流程来分析这些方法是如何被触发和如何在Service的调用中起作用的。Service有两种启动方法,startService和bindService,与广播不同,这两种启动方式的Service都需要在AndroidManifest.xml中注册。
2.2 startService
startService启动流程.jpgstartService启动流程的入口是在Activity中调用startService(intent)方法,上面这个调用图中可以看出在使用startService触发Service的时候会回调到Service中的一些方法,看最后Service中回调的方法就知道了。下面一步步讲解执行流程。
2.2.1 ContextWrapper->startService(intent)
这儿是调用的入口,开发者调用这个函数,其中的参数intent可以设置显式、也可以设置隐式调用。
2.2.2 ContextImpl->startServiceCommon(...)
这个函数中有三个参数:
- intent service:从开始的地方开发者传递下来的
- boolean requireForeground:这儿标识是否是前台service,下面Service注意点会告诉什么是前台Service
- UserHandle user:代表设备上的用户
这个函数执行了3个步骤:
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
2.2.2.1 validateServiceIntent(service)
private void validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException(
"Service Intent must be explicit: " + service);
throw ex;
} else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
}
}
}
显然在android L之后的版本,已经不允许使用隐式调用了,有一些安全的隐患,这一点要记住。也是可以杜绝一些反射带来的问题。
2.2.2.2 service.prepareToLeaveProcess(this)
设置一些状态,表明当前的service调用是否离开本进程。
2.2.2.3 binder调用到AMS
2.2.3 AMS->startService(...)
这是的参数是:
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId)
参数中是不是有很多熟悉的对象和类,跨进程的调用都会用到这些类,例如IApplicationThread caller,这是存储应用程序进程信息的对象,可以通过它找到应用程序进程,这些信息传到AMS中,是为了在system_server进程中操作service的时候校验一些安全性和权限,以及处理和应用进程相关的一些对象。
这个函数中也只是调用了一个方法调用到ActiveServices中了。
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
这个ActiveService是android中处理service的核心类,service的主要处理过程都在这里面。它是在AMS的构造函数中初始化的。下面进入ActiveServices分析此类主要执行了什么。
2.2.4 ActiveServices->startServiceLocked(...)
2.2.4.1 获取应用程序进程
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
还是这个调用方法,这就是上面讲的caller的重要作用。
2.2.4.2 搜索指定的Service
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false, false);
这个函数中主要就是查找一下是否有指定的service
- 首先从内存中去是否有满足条件的ServiceRecord,ActiveServices中用一个mServiceMap来存储一个ServiceMap的数组,这个ServiceMap中有一个mServicesByName对象。
final class ServiceMap extends Handler {
final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
}
一般先从mServiceMap中找一个匹配当前传入的intent的ServiceRecord,如果这个ServiceRecord存在,那么直接返回,不存在,继续下一步。
- 从内存中找不到,要从PMS中检索一下Service
ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
resolvedType, flags, userId, callingUid);
最终调用到PMS->resolveServiceInternalresolveServiceInternal(...)
private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
int userId, int callingUid) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForResolve(
flags, userId, intent, callingUid, false /*includeInstantApps*/);
List<ResolveInfo> query = queryIntentServicesInternal(
intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
if (query != null) {
if (query.size() >= 1) {
return query.get(0);
}
}
return null;
}
这儿调用的是queryIntentServicesInternal(...),主要是根据传入的intent来寻找匹配的ResolveInfo,在讲解PMS章节的时候再展开说说这个函数吧。
- 从PMS中检索出来的ResolveInfo不为空,取出其中的serviceInfo,接下来有一大段对当前的serviceInfo做判断的地方,serviceInfo是否是其他进程的Service,对于这儿,一般不允许执行其他进程的Service
ServiceInfo sInfo =
rInfo != null ? rInfo.serviceInfo : null;
- 如果ServiceRecord还是为空,接下来自己创建ServiceRecord,已经获得了一个ServiceRecord对象
final Intent.FilterComparison filter
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
final BatteryStatsImpl.Uid.Pkg.Serv ss;
final BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
ss = stats.getServiceStatsLocked(
sInfo.applicationInfo.uid, sInfo.packageName,
sInfo.name);
}
r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
res.setService(r);
smap.mServicesByName.put(name, r);
smap.mServicesByIntent.put(filter, r);
- 从当前的挂起Service列表中移除创建的ServiceRecord记录。
for (int i=mPendingServices.size()-1; i>=0; i--) {
final ServiceRecord pr = mPendingServices.get(i);
if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
&& pr.name.equals(name)) {
mPendingServices.remove(i);
}
}
2.2.4.3 抽取ServiceRecord属性
我们根据intent获得了当前的ServiceRecord,ServiceRecord含有一些属性,我们会根据这些属性,判断当前的service是否继续执行下去。
一个ServiceRecord记录Service的基本信息,和Activity中的ActivityRecord一样。
这儿判断的主要是Service的权限是否满足,例如当前service要操作一些uri,那是否符合权限要求。就是这里要做的事情。
2.2.4.4 获取应用进程信息
启动service之前,获取进程信息,需要判断一下,当前进程如果不存在,无法启动service
2.2.5 ActiveServices->startServiceInnerLocked(...)
这个函数中直接调用bringUpServiceLocked(...)
2.2.5 ActiveServices->bringUpServiceLocked(...)
2.2.5.1 设置不可停止状态
开始准备执行service,所以要在PMS中设置不可停止的状态,表明当前的service正在执行中,因为判断service启动的必要条件已经在上面判断过了,这儿如果没有不可抗力因素,是不会停下来的。
AppGlobals.getPackageManager().setPackageStoppedState(
r.packageName, false, r.userId);
2.2.5.2 获取当前进程--开始执行service
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
获取当前应用进程信息。
if (app != null && app.thread != null) {
app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
}
如果应用进程存在,开始执行service。
2.2.6 ActiveServices-> realStartServiceLocked(...)
- 已经开始执行service,所以要先更新LRU列表中的进程信息。
- 更新进程优先级,因为要之心service,所以当前所有进程的优先级都要调整一下,调整的规则不在这里展开说了。
- 执行create service
boolean created = false;
try {
if (LOG_SERVICE_START_STOP) {
String nameTerm;
int lastPeriod = r.shortName.lastIndexOf('.');
nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
EventLogTags.writeAmCreateService(
r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
}
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
Slog.w(TAG, "Application dead when creating service " + r);
mAm.appDiedLocked(app);
throw e;
} finally {
if (!created) {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
// Cleanup.
if (newService) {
app.services.remove(r);
r.app = null;
}
// Retry.
if (!inDestroying) {
scheduleServiceRestartLocked(r, false);
}
}
}
createService的流程执行如下:
createService流程.jpg
跨进程调用的执行代码是:
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
这个app.thread指向的就是ActivityThread中的内部类ApplicationThread
private class ApplicationThread extends IApplicationThread.Stub {
}
调用到这个对象中,然后通过在Activity定义的mainHandler来分发消息,最后调用到ActivityThread中,就是应用程序进程中,Service通用流程和方法的回调就是在这里。
2.2.7 ActivityThread->handleCreateService(...)
2.2.7.1 反射取出service
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
2.2.7.2 当前Application不存在,创建application
做过Android开发的都知道,Application在整个app中是全局的context,如果在启动service的时候application不存在。需要创建一个application
2.2.7.3 service回调方法
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
mServices.put(data.token, service);
这儿回调两个service方法,attach表示当前的service和app中上下文context连接起来。onCreate就是service运行过程中的生命周期,也是首先运行的方法。
2.2.8 ActiveServices->requestServiceBindingsLocked(...)
这儿是通过startService启动的,所以不会进入这个方法,bindService才会走到这里,注意一下。
2.2.9 ActiveServices->sendServiceArgsLocked(...)
2.2.9.1 处理pending的service列表
2.2.9.2 跨进程启动service
r.app.thread.scheduleServiceArgs(r, slice);
调用到什么地方,现在大家应该很清楚了。最终调用到的地方是handleServiceArgs
2.2.10 ActivityThread->handleServiceArgs(...)
if (!data.taskRemoved) {
res = s.onStartCommand(data.args, data.flags, data.startId);
} else {
s.onTaskRemoved(data.args);
res = Service.START_TASK_REMOVED_COMPLETE;
}
如果当前的service已经被remove掉,那么ActivityThread中,就是在应用进程中也要remove掉,如果没有,那就回调一下service的onStartCommand(...)方法。
2.3 bindService
bindService流程.jpg平时的开发中,大家一般都会采用bindService的方法,这种使用方法更容易理解,而且我们也可以更容易的回调,bindService的调用入口也是在Activity中直接调用bindService(...)
2.3.1 ContextWrapper->bindService(...)
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
return mBase.bindService(service, conn, flags);
}
3个参数,第一个参数是intent。第二个参数就是ServiceConnection,这是一个接口,我们都要实现这个接口,传入实现类的实例对象。第三个参数是一个flag,这个通常是Context.BIND_AUTO_CREATE
2.3.2 ContextImpl->bindServiceCommon(...)
2.3.2.1 传入serviceConnection到LoadedApk中
IServiceConnection sd;
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
}
传入的conn参数很重要,开发者也会用到这个回调的。
调用到LoadedApk->getServiceDispatcher(...)
传入ServiceDispatcher的构造函数中。
ServiceDispatcher(ServiceConnection conn,
Context context, Handler activityThread, int flags) {
mIServiceConnection = new InnerConnection(this);
mConnection = conn;
}
ServiceDispatcher中的mConnection对象。
2.3.2.2 验证当前的service是否符合要求
validateServiceIntent(service);
上面已经讲过。
2.3.2.3 binder调用到AMS
2.3.3 AMS->bindService(...)
2.3.4 ActiveServices->bindServiceLocked(...)
其实很多过程和startService雷同,这儿就不重复讲解了,直奔主题。前面createService的流程都是一样的。但是接下来执行的却是bind方法。
2.3.5 ActiveServices->requestServiceBindingsLocked(...)
private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
throws TransactionTooLargeException {
for (int i=r.bindings.size()-1; i>=0; i--) {
IntentBindRecord ibr = r.bindings.valueAt(i);
if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
break;
}
}
}
当前serviceRecord中记录的bindings服务遍历一下,继续处理。
2.3.6 ActiveServices->requestServiceBindingLocked(...)
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
bumpServiceExecutingLocked(r, execInFg, "bind");
r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
r.app.repProcState);
if (!rebind) {
i.requested = true;
}
i.hasBound = true;
i.doRebind = false;
} catch (TransactionTooLargeException e) {
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
throw e;
} catch (RemoteException e) {
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
return false;
}
}
这儿跨进程调用了scheduleBindService方法,会在应用进程执行执行onBind回调。
2.3.7 ActivityThread->handleBindService(...)
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManager.getService().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
如果当前的Service没有被标记为重新绑定,执行onBind回调;如果被标记为重新绑定,执行onRebind回调。
2.3.8 AMS->publishService(...)
2.3.9 ActiveServices->publishServiceLocked(...)
当前的intent如果匹配的话,直接connected(...)
try {
c.conn.connected(r.name, service, false);
} catch (Exception e) {
Slog.w(TAG, "Failure sending service " + r.name +
" to connection " + c.conn.asBinder() +
" (in " + c.binding.client.processName + ")", e);
}
这儿的c.conn就是:
ServiceDispatcher(ServiceConnection conn,
Context context, Handler activityThread, int flags) {
mIServiceConnection = new InnerConnection(this);
mConnection = conn;
}
调用到InnerConnection中的connected方法。
public void connected(ComponentName name, IBinder service, boolean dead)
throws RemoteException {
LoadedApk.ServiceDispatcher sd = mDispatcher.get();
if (sd != null) {
sd.connected(name, service, dead);
}
}
2.3.10 ServiceDispatcher->doConnected(...)
mConnection.onServiceConnected(name, service);
这就调用到ServiceConnection实现类中的继承方法中,标识当前的service已经绑定成功了。
三、Service超时
我们的service分为前台service和其他service,其他service就是后台运行的service,所谓前台service,就是调用startForegroundSerivce(...)接口实现的,前台service一般用来显示service当前的处理进度,例如下载进度,播放进度等等。
这儿讲解的超时就讲到了这两种service的超时时间是不一样的。
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;
具体的处理函数在ActiveServices->serviceTimeout(...)中。
final long now = SystemClock.uptimeMillis();
final long maxTime = now -
(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
ServiceRecord timeout = null;
long nextTime = 0;
for (int i=proc.executingServices.size()-1; i>=0; i--) {
ServiceRecord sr = proc.executingServices.valueAt(i);
if (sr.executingStart < maxTime) {
timeout = sr;
break;
}
if (sr.executingStart > nextTime) {
nextTime = sr.executingStart;
}
}
前台service超时时间:20s
后台service超时时间:200s
超过这个时间就会anr。
是否是前台service,他们的超时时间不一样。
网友评论