本篇博客旨在分析Android中Service相关的源码流程。
一、基础知识
Service通常被称之为“后台服务”,具体是指其本身的运行并不依赖于用户可视的UI界面。
例如:点击界面的音乐播放键,由Service进行实际的音乐播放工作。即使用户离开此界面,音乐仍能够继续播放。
不过Service与Activity等相同,都是运行于当前进程的主线程中。
因此一些耗时操作一般并没有放在Service中进行,而是在Service中启动一个工作线程来进行实际的操作。
1 Service的注册
在定义Service时,需要在AndroidManifest.xml中进行声明。
只有这样,PKMS在初始化时,才能通过解析AndroidManifest.xml,得到该Service的信息。
在客户端实际使用Service时,将申请服务的请求发往AMS,后者将通过PKMS提供的信息,检索到具体的Service。
Service在AndroidManifest.xml中的声明,语法格式如下所示:
<service android:enabled=["true" | "false"]
android:exported[="true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string">
<intent-filter >
<action android:name="com.android.server.TestService.TestAction" />
</intent-filter>
</service>
可以看出,Service相关的标签,与android中其它组件基本相同,此处不作赘述。
2 启动方式
一般而言,从Service的启动方式上,可以将Service分为Unbounded Service和Bounded Service。
2.1 Unbounded Service
Unbounded Service是指:客户端调用ContextImpl的startService等函数启动的Service。
这种启动方式的核心代码片段如下:
..............
Intent serviceIntent = new Intent(mContext, TestService.class);
serviceIntent.putExtra("Args", mArgs);
mContext.startService(serviceIntent);
..............
从上面的代码可以看出,客户端组件通过调用startService函数,向Service端发送一个Intent,该Intent中可以携带具体的业务请求信息。
Service启动后,会根据Intent中的请求,执行实际的业务。
通常情况下,Service启动、处理完请求后,并不会返回启动结果(除非在业务逻辑中主动进行通知)。
即使启动Service的应用组件已经被销毁了,服务将一直在后台运行。
因此,当客户端不再需要使用Service的服务时,需要主动调用ContextImpl的stopService函数。
此外,从在Service内部,也可以在执行完具体的业务后,通过stopSelf函数停止其本身。
Unbounded Service的基本实现方式如下所示:
public class TestService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
...........
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
..............
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
..........
}
}
其中,onBind函数是Service基类中的唯一抽象方法,子类必须实现。对于Unbounded Service而言,此函数直接返回 null 即可。
onCreate、onStartCommand和onDestroy都是Unbounded Service相应生命周期阶段的回调函数。
当客户端调用startService启动服务时,AMS收到请求信息后,将判断对应Service是否启动过。
如果Service还未启动,将首先回调Service的onCreate函数,然后再执行onStartCommand函数。
当客户端再次调用startService时,AMS判断Service已经启动,将只调用Service的onStartCommand函数。
在Service的onStartCommand函数中,将根据Intent的信息进行实际的业务处理。
注意到onStartCommand函数会返回一个Int型的值,该值与系统的进程管理有关,主要的取值范围如下:
START_STICKY:
当Service因为内存不足而被系统kill后,在接下来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service;
一旦创建成功后,将回调onStartCommand方法,但一般情况下,参数中的Intent将是null。
START_NOT_STICKY:
当Service因为内存不足而被系统kill后,在接下来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。
START_REDELIVER_INTENT:
与START_STICKY相同,当Service因为内存不足而被系统kill后,在接下来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service;唯一不同的是,Service创建成功后,回调onStartCommand方法时,传入的参数将是最后一次调用startService时使用的intent。
需要注意的是,只有系统kill掉Service时上述返回值才有意义,如果是人为地kill掉Service进程,系统不会按照onStartCommand的返回值重启Service。
最后,客户端无论调用多少次startService,只需要一次stopService即可将此Service终止(毕竟onCreate函数也之调用过一次),此时AMS将回调Service的onDestroy函数。
2.2 Bounded Service
Bounded Service一般使用过程如下:
1、服务端定义继承于基类Service的服务,并重写其onBind方法。
在此方法中,需要返回具体的Binder对象供客户端使用。
2、客户端通过实现ServiceConnection接口,自定义ServiceConnection对象,
在其中实现onServiceConnected函数,用于回调获取服务端提供的Binder对象。
同时,客户端也可以选择性地实现onServiceDisconnected接口,以便在与Service解绑时执行一些操作。
3、客户端调用bindService函数与Service端绑定,该函数的参数中包含服务对应的Intent和自定义的ServiceConnection对象。
与Unbounded Service一样,若Service未启动,AMS将负责启动Service,然后才会调用Service的onBind方法,得到服务端返回Binder对象。
一旦得到Binder对象后,AMS将回调ServiceConnection中实现的onServiceConnected函数,将Binder对象返回给客户端。
4、客户端得到Binder对象后,就可以通过Binder对象调用Service提供的公有函数,完成实际的业务请求。
Service端收到业务请求后,就可以执行对应的处理。
5、当客户端不再需要使用服务时,就需要通过调用unbindService函数,解除与Service的绑定。
若客户端实现了ServiceConnection的onServiceDisconnected接口,那么与Service解绑后,onServiceDisconnected将被AMS回调。
网上使用Bounded Service的代码示例较多,此处就不再具体给出,可以参考这篇博客Android总结篇系列:Android Service。
我们仅看看源码中的使用方式,例如在packages/services/mms/src/com/android/mms/service/SendRequest.java中:
/**
* Sends the MMS through through the carrier app.
*/
private final class CarrierSendManager extends CarrierMessagingServiceManager {
........
void sendMms(Context context, String carrierMessagingServicePackage,
CarrierSendCompleteCallback carrierSendCompleteCallback) {
mCarrierSendCompleteCallback = carrierSendCompleteCallback;
//调用父类CarrierMessagingServiceManager的bindToCarrierMessagingService函数
if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
.............
} else {
..............
}
}
}
看看bindToCarrierMessagingService函数:
public boolean bindToCarrierMessagingService(Context context, String carrierPackageName) {
.........
//显示指定服务的action和package
Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE);
intent.setPackage(carrierPackageName);
//客户端定义自己的ServiceConnection
mCarrierMessagingServiceConnection = new CarrierMessagingServiceConnection();
//作为客户段绑定服务端
return context.bindService(intent, mCarrierMessagingServiceConnection,
Context.BIND_AUTO_CREATE);
}
根据IntentAMS将绑定CarrierMessagingService,其onBind函数如下:
public @Nullable IBinder onBind(@NonNull Intent intent) {
if (!SERVICE_INTERFACE.equals(intent.getAction())) {
return null;
}
//mWrapper的类型为ICarrierMessagingWrapper,继承ICarrierMessagingService.Stub
//即以AIDL的方式返回Binder对象
return mWrapper;
}
最后看看客户端CarrierMessagingServiceConnection的onServiceConnected函数:
public void onServiceConnected(ComponentName name, IBinder service) {
//按照AIDL的规则,得到Binder对象
//之后,客户端就可以按照AIDL定义的接口,调用服务端的接口
onServiceReady(ICarrierMessagingService.Stub.asInterface(service));
}
3 生命周期
两种Service生命周期对应的回调函数,可以用上面这张比较经典的图来表示,其中:
Unbounded Service回调函数的顺序依次为:onCreate、onStartCommand、onDestroy;
Bounded Service回调函数的顺序依次为:onCreate、onBind、onUnbind、onDestroy。
后文分析源码时,可以看到具体的调用过程,此处不做赘述。
4 显示启动和隐式启动
最后谈一下Service的显示启动和隐式启动。
4.1 显示启动
显示启动是指:
客户端将service的具体名称写入到Intent中,此时AMS就可以直接根据名称来找到对应的service。
代码示例:
//1、startService显示启动服务
...........
Intent startIntent = new Intent();
ComponentName componentName = new ComponentName(
"com.android.server",
"com.android.server.TestService");
startIntent.setComponent(componentName);
mContext.startService(startIntent);
............
可以看到显示启动服务,就是在Intent中指定ComponentName。
ComponentName由具体的包名和类名组成。
如果启动的service与当前组件在同一个包内,也可以使用下述方式显示启动service:
//2、startService显示启动同一个包中的服务
...........
Intent startIntent = new Intent(mContext, TestService.class);
mContext.startService(startIntent);
............
同样,bindService也可以显示启动服务,对应的写法如下:
//3、bindService显示启动服务
............
Intent bindIntent = new Intent();
ComponentName componentName = new ComponentName(
"com.android.server",
"com.android.server.TestService");
bindIntent.setComponent(componentName);
mContext.bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE);
............
4.2 隐式启动
隐示启动是指:
客户端通过在intent中添加service申明的Action,此时AMS通过Action来匹配具体的service。
这种启动方式要求,service在定义Action时,要尽可能的和其它服务区分开来。
通常来说,Action将被命名为具体包名、类名和Action名的组合。
代码示例:
............
Intent startIntent = new Intent();
startIntent.setAction("com.android.server.TestService.TestAction");
mContext.startService(startIntent);
............
需要注意的是:Android 5.0以后,只能以显示方式来启动Service,隐式启动服务将抛出异常。
接下来,我们分析一下这两种类型的服务对应的源码流程。
二、Unbounded Service的启动流程
1 ContextImpl中的startService
我们从ContextImpl中的startService函数入手,看看Unbounded Service的启动流程。
public ComponentName startService(Intent service) {
.............
return startServiceCommon(service, mUser);
}
private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
//判断Intent中是否符合要求
validateServiceIntent(service);
//Intent可能跨进程传输,因此先要做一下安全性检查
service.prepareToLeaveProcess(this);
//通过AMS的接口拉起服务
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), getOpPackageName(), user.getIdentifier());
...............
return cn;
} catch (RemoteException e) {
................
}
}
从上面的代码可以看出,拉起服务还是要依赖于AMS的接口。
在这里,我们进一步看看validateServiceIntent函数:
private void validateServiceIntent(Intent service) {
//Intent中既没有指定Component,也没有指定Package,即以隐式启动的方式拉起服务
if (service.getComponent() == null && service.getPackage() == null) {
//当版本大于Android L,即Android 5.0时,会抛出异常
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException(
"Service Intent must be explicit: " + service);
throw ex;
} else {
//低版本只是打印log而已
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
}
}
}
validateServiceIntent就是上文提到的,Android 5.0后不再支持隐式启动Service的原因。
2 AMS中的startService
接下来,我们看看AMS中的startService函数:
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, String callingPackage, int userId)
throws TransactionTooLargeException {
//参数有效性检查
.................
synchronized(this) {
..........
//mServices在AMS初始化时得到,类型为ActiveServices
ComponentName res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid, callingPackage, userId);
..........
return res;
}
}
2.1 ActiveServices中的startServiceLocked
ActiveServices在AMS初始化时创建,用于管理AMS启动的Service。
上述代码进行参数有效性检查后,将调用ActiveServices的startServiceLocked函数。
我们分段看看startServiceLocked函数:
2.1.1 startServiceLocked Part-I
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, String callingPackage, final int userId)
throws TransactionTooLargeException {
................
final boolean callerFg;
if (caller != null) {
//通过AMS得到调用方的进程信息
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
................
//判断调用方是否属于前台进程
callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
} else {
callerFg = true;
}
//检索待启动的Service
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false);
..................
//从ServiceLookupResult中取出ServiceRecord
ServiceRecord r = res.record;
.................
startServiceLocked函数的第一部分,主要是利用参数信息检索出待启动的Service,这部分工作主要由retrieveServiceLocked函数完成。
由于retrieveServiceLocked使用的频率较高(后文还会遇到),同时涉及到ActivesService管理Service的一些数据结构,
因此这里深入分析一下该函数:
private ServiceLookupResult retrieveServiceLocked(.......) {
ServiceRecord r = null;
..........
//得到当前用户的UserId
userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
//每个UserId有对应的ServiceMap,统一保存在ActiveServices中
ServiceMap smap = getServiceMap(userId);
//对于显示启动
final ComponentName comp = service.getComponent();
if (comp != null) {
//根据ComponentName从ServiceMap中取出对应的ServiceRecord
r = smap.mServicesByName.get(comp);
}
//对于隐式启动
if (r == null && !isBindExternal) {
Intent.FilterComparison filter = new Intent.FilterComparison(service);
//根据Intent对应的Filter,从ServiceMap中取出匹配的ServiceRecord
r = smap.mServicesByIntent.get(filter);
}
//特殊情况的处理
//对于包含FLAG_EXTERNAL_SERVICE的service,将运行于调用方进程中
//对于这种特殊服务,
//如果根据Component或Filter找到了一个正在运行的Service,但其运行进程与当前调用进程不一致
//那么必须重新在调用进程中创建该ServiceRecord,于是将r置为null
if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
&& !callingPackage.equals(r.packageName)) {
// If an external service is running within its own package, other packages
// should not bind to that instance.
r = null;
}
//以上均是在缓存信息中,查找ServiceRecord
//如果查询不到,则必须通过PKMS进行查找
if (r == null) {
try {
//PKMS根据参数得到对应Pkg中Serivce的ResolveInfo
ResolveInfo rInfo = AppGlobals.getPackageManager().resolveService(service,
resolvedType, ActivityManagerService.STOCK_PM_FLAGS
| PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
userId);
//从ResolveInfo中取出ServiceInfo
ServiceInfo sInfo =
rInfo != null ? rInfo.serviceInfo : null;
//构造出Service对应的ComponentName
ComponentName name = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
//特殊情况处理
if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
if (isBindExternal) {
..............
// Run the service under the calling package's application.
//FLAG_EXTERNAL_SERVICE将运行在调用方进程中,此处就是修改PKMS检索出的ServiceInfo
//先得到调用方的应用信息
ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
............
//将ServiceInfo中的信息,改为调用方应用的信息
sInfo = new ServiceInfo(sInfo);
sInfo.applicationInfo = new ApplicationInfo(sInfo.applicationInfo);
sInfo.applicationInfo.packageName = aInfo.packageName;
sInfo.applicationInfo.uid = aInfo.uid;
name = new ComponentName(aInfo.packageName, name.getClassName());
service.setComponent(name);
} else {
//抛出异常
.....
}
} else if(isBindExternal) {
//抛出异常
............
}
//多用户的处理
if (userId > 0) {
//检查服务是否为单例且可被调用的
if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
sInfo.name, sInfo.flags)
&& mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
//对于多用户而言,每个用户启动的服务,运行于对应用户所在进程组中
//但如果待启动服务为单例的,那么该服务还是得运行在系统用户的进程组中
//于是此次将userId置为0
userId = 0;
//ServiceMap都被调整为系统用户对应的
smap = getServiceMap(0);
}
sInfo = new ServiceInfo(sInfo);
//此处使用了userId
sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId);
}
r = smap.mServicesByName.get(name);
if (r == null && createIfNeeded) {
..............
//创建出对应的ServiceRecord
r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
..............
//保存到ServiceMap中
smap.mServicesByName.put(name, r);
smap.mServicesByIntent.put(filter, r);
}
} catch(RemoteException ex) {
...........
}
}
if (r != null) {
//进行一些权限检查和有效性检查
.................
//没有问题时,返回正常结果
return new ServiceLookupResult(r, null);
}
return null;
}
retrieveServiceLocked函数看起来比较长,但逻辑还是比较清晰的,最主要的步骤是:
1、从ActiveServices保存的一些服务对应的记录信息中,找出ServiceRecord;
2、如果没有找到ServiceRecord,就通过PKMS找出对应的Service信息,然后重新构建出ServiceRecord;
3、将ServiceRecord保存到ActiveServices的数据结构中。
其中,由于考虑到了带有FLAG_EXTERNAL_SERVICE的Service及单例Service的情况,
因此单独进行了处理,在了解整体逻辑后,单独看这些特殊处理还是比较好理解的。
ActiveServices中,保存ServiceRecord涉及的数据结构主要如下图所示:
2.1.2 startServiceLocked Part-II
接下来我们继续看看startServiceLocked下一部分的内容:
//进行一些检查工作
.............
//如果这个服务在重启列表中,清空对应的信息
if (unscheduleServiceRestartLocked(r, callingUid, false)) {
.................
}
r.lastActivity = SystemClock.uptimeMillis();
r.startRequested = true;
r.delayedStop = false;
//startService可以多次向Service传递信息,每次的信息都是一个StartItem,对应着一个StartId
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, neededGrants));
............
//addToStarting决定是否将待启动的Service
//加入到ActiveServices维护的mStartingBackground队列
boolean addToStarting = false;
//如果启动服务的不是前台进程
//同时服务对应的ServiceRecord中没有记录对应进程的信息(即初次使用)
if (!callerFg && r.app == null
//并且user已经启动过其它进程
&& mAm.mUserController.hasStartedUserState(r.userId)) {
//通过AMS查询Service对应的进程信息
ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
//若Service对应的进程未启动,或优先级过低,则有可能需要延迟启动服务
if (proc ==