从源码的角度看Service是如何启动的

欢迎访问我的个人博客 ,原文链接: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启动的情况

流程图1

接下来我们就看看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方法。


流程图2

realStartServiceLocked

这里需要对上面的app.thread做一下特殊的说明。如果你已经了解了Binder的通信机制,那你应该知道一般我们的服务都是由客户端向服务端发出请求,接着服务端向客户端返回结果,这个是单向的通信,但是如果反过来服务端要向客户端发送请求的话,那么同样的,在服务端也应该持有另外一个Proxy,而在客户端也同样需要一个Manager与之对应。
在这里app是要运行 Service 的进程对应的ProcessRecord对象,代表一个应用进程,而thread是一个ApplicationThreadProxy对象,它运行在AMS(现在AMS就是客户端了),而与之对应的服务端则是在应用程序中的ApplicatonThread,还是有点绕,我们用一张图来展示他们的关系。

两对Binder

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

还需要再唠叨一下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启动的全过程。


流程图3

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哈哈~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 前言 在安卓开发过程中我们除了Activity之外最常用的组件就是Service,特别是在和用户没有界面交互的终端...
    samychen阅读 533评论 0 2
  • 转载请注明文章出处LooperJing! 这几天分析了一下Activity的启动过程和Service的启动过程,于...
    LooperJing阅读 4,627评论 12 21
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 一直以来有过念头想要写作,一方面自我感觉文字功底不错,另一方面却又是很是害怕暴露自己的内心,但是明明很多时候智慧的...
    清透阅读 766评论 1 0
  • 我们都生活在自己的泡泡里,这个泡泡就是我们所熟悉的一切。我固然也有我的泡泡,例如这个灰红相间的校园,例如这些叽叽喳...
    东方眼烁破四天下阅读 470评论 4 13