欢迎访问我的个人博客 ,原文链接:http://wensibo.top/2017/07/16/service/ ,未经允许不得转载!

七月中旬了,大家的实习有着落了吗?秋招又准备的怎么样了呢?我依旧在准备着秋招,每当想到自己以应届生的身份找着工作而工作却不一定要你的时候,难免也会有点失落。互联网行业的大佬们求贤若渴但对贤才也十分的苛刻,看到内推正如火如荼的进行着,深怕自己被这场浪潮甩在身后,所以也不得不苦心的准备着。如果你也是2018届应届生,如果你也看到了这篇文章,请你在留言区留下你找工作,准备秋招的感受,我们一起交流交流。

今天接着上篇文章一起来看看四大组件的老二——Service。话不多说我们开始吧!

前言

我们一般使用Service有两种方式,startService和bindService,这两种方法使用场景各有不同,本篇文章以startService为例讲解Service的启动过程,而bindService大体上与startService相近,只是一些逻辑调用上有所区别。

在这里我先贴上通过本次分析得到的Service完整的启动流程图,现在不需要理解其中的过程,只需要一步步分析源码的时候回过头来看看这幅图,以免迷失方向。当然我在每一步都会贴出相对应的流程图。

认识ContextImpl

首先先给出一张类图,我们从大局上看一下这些类的关系。



从上面这张图我们可以看到Activity继承了ContextWrapper类,而在ContextWrapper类中,实现了startService方法。在ContextWrapper类中,有一个成员变量mBase,它是一个ContextImpl实例,而ContextImpl类和ContextWrapper类一样继承于Context类。为什么会给出这张图呢?这对我们接下来的分析十分有用。

启动Service的入口

我们在Activity中使用startService(Intent service)来启动一个服务,其调用的方法如下:

//ContextWrapper类
@Override
public ComponentName startService(Intent service) {
return mBase.startService(service);
}

可以看到其调用了ContextWrapper的startService方法,而这个方法内部使用mBase的startService方法,我们再看回刚才的类图,可以看到,这里的mBase其实就是ContextImpl,从而我们可以得出ContextWrapper类的startService方法最终是通过调用ContextImpl类的startService方法来实现的。那接下来就来看看ContextImpl.startService()。

@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, mUser);
} private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), 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());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

我们可以看到startService方法其实是调用了startServiceCommon,而startServiceCommon做了些什么呢?我们看到了一个熟悉的家伙:ActivityManagerNative.getDefault(),在上篇文章分析Activity启动的时候我们也看过他,并且我们也知道ActivityManagerNative.getDefault()返回的就是一个ActivityManagerProxy对象,这里使用Binder机制(如果你对Binder机制不是很了解,那可以看一下我之前写的这篇文章)将代理Proxy返回给客户端,而客户端通过将参数写入Proxy类,接着Proxy就会通过Binder去远程调用服务端的具体方法,因此,我们只是借用ActivityManagerProxy来调用ActivityManagerService的方法。所以这里其实是调用了远程ActivityManagerService的startService方法。

到这里我们先用流程图来看一下目前Service启动的情况



接下来我们就看看ActivityManagerService是如何实现的。

AMS如何进一步启动Service

//ActivityManagerService类
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, int userId) {
//...
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
}

可以看到这里调用了mService的startServiceLocked方法,那mService是干嘛的呢?此处的mServices是一个ActiveServices对象,从名字上也能看出该类主要是封装了一些处于活动状态的service组件的方法的调用。那接下来就看看他的startServiceLocked是如何实现的。

ComponentName startServiceLocked(IApplicationThread caller,
Intent service, String resolvedType,
int callingPid, int callingUid, int userId) {
//...
final boolean callerFg;
if (caller != null) {
//mAm 是ActivityManagerService.
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when starting service " + service);
}
callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
} else {
callerFg = true;
}
ServiceLookupResult res = retrieveServiceLocked(service, resolvedType,
callingPid, callingUid, userId, true, callerFg);
if (res == null) {
return null;
}
if (res.record == null) {//权限拒绝.
return new ComponentName("!", res.permission != null
? res.permission : "private to package");
} ServiceRecord r = res.record;
//
r.lastActivity = SystemClock.uptimeMillis();
r.startRequested = true;
r.delayedStop = false;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, neededGrants)); final ServiceMap smap = getServiceMap(r.userId);
boolean addToStarting = false;
//...
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}

代码稍微有点长,但是最重要的逻辑在于最后一句的startServiceInnerLocked方法,他的内部实现是这样的。

ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
ServiceRecord r, boolean callerFg, boolean addToStarting) {
//...
//真正开启service的地方。
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
//注意此处反回值是null的时候,证明没有异常.
if (error != null) {
return new ComponentName("!!", error);
}
//...
return r.name;
}

startServiceInnerLocked方法内部调用了bringUpServiceLocked方法来进行后续的启动。

private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting) throws TransactionTooLargeException { //...
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
return null;
} //... return null;
}

在bringUpServiceLocked方法中调用了realStartServiceLocked方法,在Activity的启动过程中我们也曾看过相似的方法,说明到了这里我们也快看到真正的Service启动了,接着来看realStartServiceLocked。

private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException { //... boolean created = false;
try { //...
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;
} //这里会调用Service的onStartCommand
sendServiceArgsLocked(r, execInFg, true); //... }

我们先用流程图看看Service启动的逻辑。接着在下面会着重讲解上方的realStartServiceLocked方法。

realStartServiceLocked

这里需要对上面的app.thread做一下特殊的说明。如果你已经了解了Binder的通信机制,那你应该知道一般我们的服务都是由客户端向服务端发出请求,接着服务端向客户端返回结果,这个是单向的通信,但是如果反过来服务端要向客户端发送请求的话,那么同样的,在服务端也应该持有另外一个Proxy,而在客户端也同样需要一个Manager与之对应。

在这里app是要运行 Service 的进程对应的ProcessRecord对象,代表一个应用进程,而thread是一个ApplicationThreadProxy对象,它运行在AMS(现在AMS就是客户端了),而与之对应的服务端则是在应用程序中的ApplicatonThread,还是有点绕,我们用一张图来展示他们的关系。



相信通过上面的图示大家应该能够明白应用程序进程与系统服务进程之间的双向通信了吧!

还需要再唠叨一下ApplicationThread与ApplicationThreadProxy之间的关系,我们通过这两个类的定义可以更深刻的理解他们的关系

  • IApplicationThread
public interface IApplicationThread extends IInterface

IApplicationThread其实是一个IBinder类型的接口。并且在这个接口中声明了许多与Activity,Service生命周期相关的方法,那么它的实现类又是谁呢?答案就是ApplicationThreadNative

  • ApplicationThreadNative
public abstract class ApplicationThreadNative extends Binder implements IApplicationThread

可以看到ApplicationThreadNative是一个抽象类,我们不能直接创建其对象,应该使用其子类,而恰好ApplicationThread就是其子类

  • ApplicationThread
private class ApplicationThread extends ApplicationThreadNative

ApplicationThread就是真正意义上的服务端,它的父类ApplicationThreadNative就是将具体的操作将给它来执行的。

  • ApplicationThreadProxy
class ApplicationThreadProxy implements IApplicationThread

说了那么多,在客户端运行的ApplicationThreadProxy在哪里呢?其实如果理解了Binder机制,那么我们应该知道他就是ApplicationThreadNative的内部类,客户端(AMS)就是通过它与service所在的进程进行通信的。因此我们接着要看的当然是ApplicationThread的scheduleCreateService了。

ApplicationThread.scheduleCreateService

public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo; sendMessage(H.CREATE_SERVICE, s);
}

可以看到在scheduleCreateService方法中发送了一个message,其消息类型为CREATE_SERVICE,它是在H类中定义的一个常量,而H类其实就是继承于Handler的,它专门用来处理发送过来的请求。接下来就来看看它是如何处理创建service这个消息的。

H.handleMessage

public void handleMessage(Message msg) {
switch (msg.what) {
...
case CREATE_SERVICE:
handleCreateService((CreateServiceData)msg.obj); //【见流程15】
break;
case BIND_SERVICE:
handleBindService((BindServiceData)msg.obj);
break;
case UNBIND_SERVICE:
handleUnbindService((BindServiceData)msg.obj);
break;
...
}
}

可以看到handleMessage处理了很多类型的消息,包括service的创建、绑定、解绑、销毁等等,我们直接看创建的逻辑,也就是handleCreateService方法。不过有一个问题,那就是Handler创建之前必须要创建Looper,否则会报错,那么Looper是在哪里创建的呢?答案就是ActivityThread的main方法。

public static void main(String[] args) {
//...
//在主线程创建Looper
Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread();
thread.attach(false); if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
} AsyncTask.init();
Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}

我们可以看到Looper就是在这里创建的,而Handler也是在Service进程中的主线程,也就是说它处理消息也是在主线程,那么Service的创建自然也就是在主线程中。可是ActivityThread是什么鬼呢?其实刚才的ApplicationThread就是它的内部类。

接下来继续看Handler如何处理消息。

private void handleCreateService(CreateServiceData data) {
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo); java.lang.ClassLoader cl = packageInfo.getClassLoader();
//通过反射创建目标服务对象
Service service = (Service) cl.loadClass(data.info.name).newInstance();
... try {
//创建ContextImpl对象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
//创建Application对象
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//调用服务onCreate()方法
service.onCreate();
mServices.put(data.token, service);
//调用服务创建完成
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (Exception e) {
...
}
}

在这里Handler通过反射机制拿到Service对象,于是就调用了service的onCreate方法,所以Service就算是启动了。我们通过层层的寻找总算是见到了onCreate的庐山真面目。

我们依旧用流程图来展示一下Service启动的全过程。

onStartCommand

可是还有另外一个重要的方法onStartCommand呢?不急,我们刚才在sendServiceArgsLocked方法中还有另外一个sendServiceArgsLocked方法没有讲到,他就是onStartCommand的入口,我们看看他是如何实现的。

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
boolean oomAdjusted) {
final int N = r.pendingStarts.size();
if (N == 0) {
return;
} while (r.pendingStarts.size() > 0) {
try {
//与调用scheduleCreateService方法一样,远程调用ApplicationThread的scheduleServiceArgs方法
r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
} catch (RemoteException e) {
// Remote process gone... we'll let the normal cleanup take
// care of this.
if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r);
break;
} catch (Exception e) {
Slog.w(TAG, "Unexpected exception", e);
break;
}
}
}

我们看到这里依旧调用了远程服务端ApplicationThread的方法来执行后面的逻辑,其实通过上面分析onCreate的逻辑大家应该能够知道调用onStartCommand也是大同小异的,具体的调用逻辑就交给大家去分析啦!

后记

到这里呢,我们就把Service的启动流程完整的讲解了一遍,希望大家通过这篇文章能够对Service更加的了解,毕竟四大组件中Service的地位还是举足轻重的,并且在面试中也会经常被问到,希望大家逢问必会O(∩_∩)O哈哈~

从源码的角度看Service是如何启动的的更多相关文章

  1. 从源码的角度看Activity是如何启动的

    欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 大家好,今天想与大家一起分享的是Activity.我们平时接触的最多的 ...

  2. 从源码的角度看 React JS 中批量更新 State 的策略(下)

    这篇文章我们继续从源码的角度学习 React JS 中的批量更新 State 的策略,供我们继续深入学习研究 React 之用. 前置文章列表 深入理解 React JS 中的 setState 从源 ...

  3. 从源码的角度看 React JS 中批量更新 State 的策略(上)

    在之前的文章「深入理解 React JS 中的 setState」与 「从源码的角度再看 React JS 中的 setState」 中,我们分别看到了 React JS 中 setState 的异步 ...

  4. 从源码的角度再看 React JS 中的 setState

    在这一篇文章中,我们从源码的角度再次理解下 setState 的更新机制,供深入研究学习之用. 在上一篇手记「深入理解 React JS 中的 setState」中,我们简单地理解了 React 中 ...

  5. Android AsyncTask完全解析,带你从源码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...

  6. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  7. 从源码的角度分析ViewGruop的事件分发

    从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...

  8. 从源码的角度解析View的事件分发

    有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图 ...

  9. 第九节:从源码的角度分析MVC中的一些特性及其用法

    一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...

随机推荐

  1. Gradle 使用Maven本地缓存

    如果想使用Maven本地缓存,需要定义:build.gradle 文件下定义 build.gradle repositories { mavenLocal() } Gradle使用与Maven相同的策 ...

  2. mysql5.6 rpm安装配置

    检查MySQL及相关RPM包,是否安装,如果有安装,则移除(rpm –e 名称)   [root@localhost share]# rpm -qa | grep -i mysql MySQL-cli ...

  3. 六一儿童节PHP宝宝又被围剿了,迅速围观!

    原文链接 据说这是2020年某国际多语言幼儿园的官方讨论会现场外泄图片,程序员宝宝们,你们同意会议结果吗? 据说这是2020年某国际多语言幼儿园的官方讨论会现场外泄图片,程序员宝宝们,你们同意会议结果 ...

  4. python 2.7中urllib 2 与python 3.5中 urllib的区别。

    python 3.x中urllib库和urilib2库合并成了urllib库. 其中urllib2.urlopen()变成了urllib.request.urlopen() urllib2.Reque ...

  5. Redis 小白指南(三)- 事务、过期、消息通知、管道和优化内存空间

    Redis 小白指南(三)- 事务.过期.消息通知.管道和优化内存空间 简介 <Redis 小白指南(一)- 简介.安装.GUI 和 C# 驱动介绍> 讲的是 Redis 的介绍,以及如何 ...

  6. 连接Oracle数据库的时候报了“Got minus one from a read call”

    (转) 出现这种问题基本上就以下几种原因,可以查一下系统日志看看:1:数据库连接满了,扩大数据库连接池2:所登录的机子IP不在sqlnet.ora内,加入后重启listerner即可3:数据库负载均衡 ...

  7. javascript痛点之二作用域链

    1.执行环境(执行上下文) 先看段代码 var a = 10; var b = 20; function cc(){ var c = 30; alert("b="+b); } cc ...

  8. javaCV开发详解之4:转流器实现(也可作为本地收流器、推流器,新增添加图片及文字水印,视频图像帧保存),实现rtsp/rtmp/本地文件转发到rtmp流媒体服务器(基于javaCV-FFMPEG)

    javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...

  9. python编码问题大终结

    一.了解字符编码的知识储备 1. 文本编辑器存取文件的原理(nodepad++,pycharm,word) 打开编辑器就打开了启动了一个进程,是在内存中的,所以在编辑器编写的内容也都是存放与内存中的, ...

  10. jQuery 插件 的this 指向问题(实战)

    daterangepicker 是一个JavaScript组件,用来选择日期. 资源直接搜索 daterangepicker 即可,当然好看的样式可以基于Bootstrap. 官网:http://ww ...