Android6.0之App的Service组件运行机制之StartService

现在来分析service是如何启动的。

service运行的进程

一般在AndroidManifest.xml配置service组件信息时,都会设置android:process属性,其值的格式“:XXX”,如下所示:

<service android:name="com.godin.demo.tmp.TmpService1"
                 android:process=":p1"></service>

这样的话,AMS在启动这个service的时候,会为其创建一个名为"包名:XXX"的进程,在进程中运行这个service。

如果不设置android:process属性,那么service就可能会和其他组件,例如activity运行在一个进程中的UI线程,也就是主线程中。那么在这种情况下,service中执行耗时的操作是比较危险的。最后会解释原因。

service的启动方式:

  1. startService(),启动service之后,启动者和service之间的关系比较松散,启动者仅能停止service,不能和service之间交互。而且启动者,比如activity被销毁后,service不受影响,还可以继续运行。

  2. bindService(),启动的service和启动者可以交互,启动者可以调用service的方法,即rpc。启动者销毁后,如果没有其他组件绑定该service,那么该service也被销毁。

两个方法都是异步的,也就是说当这两个方法返回的时候,service可能还没创启动好,还没执行相应的回调方法。

不管是哪种启动方式,终究还是由AMS来启动的,因为service毕竟是一个组件,而AMS是组件的管理者。

startService启动service

startService()方法实际上ContextImpl.startService():

public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, mUser);
    }

private ComponentName startServiceCommon(Intent service, UserHandle user) {
    try {
        validateServiceIntent(service);
        service.prepareToLeaveProcess();
        // 通过AMS的代理对象,向AMS发起rpc,调用其startService
        ComponentName cn = ActivityManagerNative.getDefault().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), getOpPackageName(), user.getIdentifier());
        ..........
        return cn;
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
}

通过以上代码可知,实际上调用AMS.startService()方法来启动service:

public ComponentName startService(
        IApplicationThread caller, // 发起者的进程的ActivityThread.mApplication,即ApplicationThread  binder对象的代理binder
        Intent service,//启动service的intent
        String resolvedType,// 没用
        String callingPackage,// 发起者的包名
        int userId)
        throws TransactionTooLargeException {
    ...............
    synchronized(this) {
      ..........
        // mServices是ActiveService对象
        ComponentName res = mServices.startServiceLocked(caller, service,
                resolvedType, callingPid, callingUid, callingPackage, userId);
        Binder.restoreCallingIdentity(origId);
        return res;
    }

启动service的过程大体上是:

  1. 首先检查AMS是否已经存在该service对应的ServiceRecord,存在的话,说明该service已经启动了;

  2. 如果service还没启动,那么从PMS中检查该service是否已被安装,也就是说来自某个安装的apk;

  3. 如果servic要求寄宿在的进程还没有被创建,那么就要创建一个符合要求的进程;

  4. 在service要求寄宿的进程中,创建service对象,并执行其生命周期方法;

接下来分析ActiveService.startServiceLocked:

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
           int callingPid, int callingUid, String callingPackage, int userId)
           throws TransactionTooLargeException {
       if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
               + " type=" + resolvedType + " args=" + service.getExtras());

       final boolean callerFg;
       // 检查调用者是否处于前台,是的话设置callerFg为true
       if (caller != null) {
           final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
           if (callerApp == null) {
              .........
           }
           callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
       } else {
           callerFg = true;
       }

      // 检查AMS是否存在该service的ServiceRecord,没的话创建
       ServiceLookupResult res =retrieveServiceLocked(service, resolvedType, callingPackage,
                   callingPid, callingUid, userId, true, callerFg);
      .........
      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));

retrieveServiceLocked()的作用就是检查和创建ServiceRecord,过程如下:

android_app_service-3.png

由上图可知,是通过getServiceMap()拿到一个ServiceMap,然后从中检查是否存在该service的ServiceRecord.

Android 系统是支持多用户的,所以系统把一个用户下面运行的service,都方到了ServiceMap结构中,可以通过用户ID拿到这个结构:

 class ServiceMap extends Handler {
   final int mUserId;
   final ArrayMap<ComponentName, ServiceRecord> mServicesByName
           = new ArrayMap<ComponentName, ServiceRecord>();
   final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent
           = new ArrayMap<Intent.FilterComparison, ServiceRecord>();

   final ArrayList<ServiceRecord> mDelayedStartList
           = new ArrayList<ServiceRecord>();
  final ArrayList<ServiceRecord> mStartingBackground
           = new ArrayList<ServiceRecord>();
  .............
 }

其中mServicesByName这个ArrayMap是以service的组件名为key的,而mServicesByIntent是以启动这个service的intent为key的。只要是运行着的service至少在这两者置一中进行了记录,所以可以通过这两个map快速查找service是否已经运行。运行的话,返回其ServiceRecord.

如果这两个map中都没有,那就创建一个ServiceRecord,并添加到这两个map中。

retrieveServiceLocked()返回的ServiceLookupResult类:

private final class ServiceLookupResult {
        final ServiceRecord record;
        final String permission;

        ServiceLookupResult(ServiceRecord _record, String _permission) {
            record = _record;
            permission = _permission;
        }
    }

可以看到是对ServiceRecord的简单二次封装,其中permission是用来启动这个service时需要的权限。比如有的app中的service,在AndroidManifest.xml还配置了启动他的权限,那么这个权限就记录在这里。

那么继续分析startServiceLocked():

// 拿到前面找到的ServiceRecord
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));

这段代码的无非就是设置一下ServiceRecord的一些字段,注意这里没有设置ServiceRecord.app,也就是说如果这个ServiceRecord对象是新创建的,那么它还没有与进程关联,此时r.app为NULL.如果ServiceRecord已经存在,也就是service已经启动了,那么r.app就不会为null。

重点看r.pendingStarts.add操作。这个操作将启动service所需要的信息,如intent等都保存了起来,只要这个pendingStarts不为null,就说明有启动该service的请求还没处理。当后面处理完之后,会将存储的信息从r.pendingStarts移动到r.deliveredStarts中。这两个成员都是ArrayList<StartItem>类型的数组。

继续分析startServiceLocked(),略过启动service的发起者不处于前台的情况,那么:

 return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);

这个方法中又是通过bringUpServiceLocked()来启动的service的,此时应该可以分为如下几种情况:

  1. service已经启动了,也就是说前面找到的ServiceRecord是从ServiceMap中获得,并没有创建ServiceRecord。而且已经与ProcessRecord关联,即r.app不为null。这种情况下就很简单了,调用 sendServiceArgsLocked()就可以了,这最终会导致app中service.onStartCommand()方法被执行。

  2. 如果service要求寄宿的进程还没被创建,那么就要通过AMS.startProcessLocked()创建一个进程

  3. 如果service要求寄宿的进程已经存在,而且也没有和前面找到的service的ServiceRecord关联那么调用realStartServiceLocked(),这最终会依次调用到app中service.onCreate()和onStartCommand()这两个service的生命周期方法。

先看第一种情况:

android_app_service-4.png

当要启动的service已经启动的话,ServiceRecord.app.thread 就是这个service所在进程的ActivityThread.mAppThread 这个binder实体在AMS中的代理binder。那么就可以通过这个代理binder跨进程调用service所在进程的ActivityThread.mAppThread.scheduleServiceArgs()方法:

public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
     int flags ,Intent args) {
     ServiceArgsData s = new ServiceArgsData();
     s.token = token;
     s.taskRemoved = taskRemoved;
     s.startId = startId;
     s.flags = flags;
     s.args = args;
     sendMessage(H.SERVICE_ARGS, s);
 }

传入的参数token,是该service在AMS中ServiceRecord 这个binder实体的代理binder;

taskRemoved传入的是fasle;

flags是启动service时的flag。

intent是启动这个service的intent。

通过这里的sendMessage()方法,就可以确定startService()是一个异步方法了。因为到这里就会返回了。接下来的消息处理是一个异步的过程,startService()不会等到消息处理完之后才返回。

private void handleServiceArgs(ServiceArgsData data) {
        // 根据ServiceRecord代理binder,在ActivityThread.mServices中索引到对应的service
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                if (data.args != null) {
                    data.args.setExtrasClassLoader(s.getClassLoader());
                    data.args.prepareToEnterProcess();
                }
                int res;
                if (!data.taskRemoved) {
                    res = s.onStartCommand(data.args, data.flags, data.startId);
                    ...........

实际上后两种情况,可以归结为service还没启动这一大类中去。

暂时跳过创建App进程的情况,后面会单独分析App进程的创建过程。这里我们只要知道AMS会根据app的组件需求,例如某个组件设置了android:processName指定了另外一个进程名字,而这个进程有不存在,那么AMS会向zygote进程发出创建进程的请求,zygote创建进程之后,首先执行的代码是ActivityThread.main()方法即可。

假设service要求寄宿的进程已经创建好了,过程如下所示:


android_app_service-5.png

在realStartServiceLocked()方法中会跨进程调用service寄宿的进程的handleCreateService()方法。在该方法中先通过getPackageInfoNoCheck()得到要启动的service的代码所在的apk在进程中的代表:LoadedApk对象。这个对象中记录了加载该apk的classloader,然后利用loadclass,装载要启动的service的类,并通过newInstance()创建了一个service对象。

紧接着为service组件创建上下文context,然后通过makeApplication()方法拿到其所在进程的application对象,然后调用service.attch()和service.OnCreate()方法:

Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
        ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);

service.attach()将ServiceRecord在service进程中的代理binder保存在了service.mToken中,并且作为key保存在ActivityThread.mServices中,value是service/

service.attach()方法执行之后,才会执行service的第一个生命周期方法onCreate().

由前面的时序图可知,realStartServiceLocked()中先跨进程调用service所在进程的scheduleCreateService()方法,该方法发送了一个消息CREATE_SERVICE之后便会返回这是一个异步处理的过程。

然后调用realStartServiceLocked()又调用sendServiceArgsLocked()方法,该方法在发送一个SERVICE_ARGS消息。

这两个异步消息均通过service所在进程的handler发送, 都在service所在的主线程中的looper中被处理,而且线处理CREATE_SERVICE,后处理SERVICE_ARGS消息。也就是先调用service.onCreate(),后执行service.onStartCommand()方法。

但实际上service中最先执行的是service.attach()方法。

CREATE_SERVICE和SERVICE_ARGS 这两个消息都实在进程的主线程也就是ui线程中执行的,所以onCreate()和onStartCommand()有太耗时的操作时,要在开启一个线程来成执行。

到这里为止应该对startService()启动service的过程有了大体上的了解了。

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

推荐阅读更多精彩内容