通过 startService 在新进程中启动服务的流程(一)

Android 启动 Service 的方式有两种:startService 和 bindService,本文通过分析 Android 源码对 startService 流程中比较重要的几个点进行展开。

1. 基本流程

每当系统决定在新进程中启动一个Activity或者Service的时候,ActivityManagerService(下面统称 AMS)会通过调用 startProcessLocked 函数为应用程序启动新的进程,图 1.1 和图 1.2 分别为 Android 4.4 和 Android 4.0 在新进程中起一个Service的过程(实际上4.2之前的版本,AMS 都是通过调用自己的startServiceLocked,从4.2开始 startServiceLocked 是 ActiveServices 的一个函数,AMS 中则包含一个 ActiveServices 的字段 mServices,转而调用 mServices.startServiceLocked 方法)。
<center>

启动Service.jpg

图 1.1 context.startService 基本流程时序图(Android4.4)</center>
<center>

startService.png

图 1.2 context.startService 基本流程时序图(Android4.0)</center>
为简单起见,我们通过 Android 4.0 源码来说明,从图 1.2 可以看出,关键流程有 7 个,其中1、2均为简单的调用,源码一看便知,3为Binder远程调用,其具体实现不在本文的程序启动流程讨论范围内。我们只需要关注 ActivityManagerService.startServiceLocked 该方法,即流程 5-7 即可。

2. 验证startService调用者进程是否存在

本节讨论的即图 1.2 中的流程 5,AMS 在执行 startServiceLocked 时首先要确定调用者是否还存在,如果不存在将会抛出 SecurityException 的异常,如下为对应的源码(frameworks/base/services/java/com/android/server/am/ActivityManagerService.java):

ComponentName startServiceLocked(IApplicationThread caller, 
                                 Intent service, 
                                 String resolvedType, 
                                 int callingPid, 
                                 int callingUid) { // 这个排版当然不是 Android 源码的
    synchronized(this) {
        if (DEBUG_SERVICE) Slog.v(TAG, "startService: " + service
                + " type=" + resolvedType + " args=" + service.getExtras());

        if (caller != null) {
            final ProcessRecord callerApp = getRecordForAppLocked(caller);
            if (callerApp == null) {
                throw new SecurityException(
                        "Unable to find app for caller " + caller
                        + " (pid=" + Binder.getCallingPid()
                        + ") when starting service " + service);
            }
        }

        ......
        
    }
}

从上面的代码可以看出 AMS 通过 getRecordForAppLocked 获取(当前运行的进程中)对应调用者的 ProcessRecord 对象,该函数展开如下源码:

final ProcessRecord getRecordForAppLocked(
        IApplicationThread thread) {
    if (thread == null) {
        return null;
    }

    int appIndex = getLRURecordIndexForAppLocked(thread);
    return appIndex >= 0 ? mLruProcesses.get(appIndex) : null;
}

private final int getLRURecordIndexForAppLocked(IApplicationThread thread) {
    IBinder threadBinder = thread.asBinder();

    // Find the application record.
    for (int i=mLruProcesses.size()-1; i>=0; i--) {
        ProcessRecord rec = mLruProcesses.get(i);
        if (rec.thread != null && rec.thread.asBinder() == threadBinder) {
            return i;
        }
    }
    return -1;
}

通过比对 IBinder 对象是否一样来判断是否是该调用者的 ProcessRecord(至于为什么这样判断在谈到 ActivityThread 时再说),这里用到一个字段 mLruProcesses,它包含了所有当前运行的程序的 ProcessRecord 对象,每当一个新(app)进程被启动时, ActivityThread 就会被创建(实际上会被直接 Process.start 执行),并往 mLruProcesses 中插入自己的 ProcessRecord 对象,这点在分析 ActivityThread.attach 过程时还会提到。

3. retrieveServiceLocked 如何查找/创建该服务对应的 ServiceRecord

在检查完调用者存在对应进程后,通过 retrieveServiceLocked 函数解析传来的 Intent 对象并生成一个 ServiceLookupResult 对象(包含一个 ServiceRecord 对象):


ComponentName startServiceLocked(IApplicationThread caller, 
                                 Intent service, 
                                 String resolvedType, 
                                 int callingPid, 
                                 int callingUid) {
    synchronized(this) {
        
        ......
        
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType,
                    callingPid, callingUid);
        if (res == null) {
            return null;
        }
        if (res.record == null) {
            return new ComponentName("!", res.permission != null
                    ? res.permission : "private to package");
        }
        ServiceRecord r = res.record;
        
        ......
        
    }
}

startServiceLocked 函数返回类型是 ComponentName,直接返回给ContextImpl之后再判断该ComponentName 是否可用,只有当找不到指定 Service 或者 system_server 出什么异常时 startServiceLocked 才会返回一个 null,如果指定的是没有导出的服务,会返回一个包名为“!”(这是个很有意思的细节)的 ComponentName,如下 ContextImpl 源码(frameworks/base/core/java/android/app/ContextImpl.java):

if (cn != null && cn.getPackageName().equals("!")) {
    throw new SecurityException(
        "Not allowed to start service " + service + " without permission " + cn.getClassName());
}

3.1 ServiceRecord 的基本信息

ServiceRecord,我们可以称之服务记录,也可以叫服务描述符,每一个运行中的服务都会在 AMS 的 HashMap<ComponentName, ServiceRecord> mServices 字段中存储一份自己的 ServiceRecord,该类记录了一个 Service 的状态信息,其中包括:



1. final ActivityManagerService ams;
2. final BatteryStatsImpl.Uid.Pkg.Serv stats; //可以获取服务启动所耗时间、被启动的次数、被调用的次数
3. final ComponentName name; //此服务组件名信息
4. final String shortName; //服务名 name.flattenToShortString().
5. final Intent.FilterComparison intent; //用来搜寻该服务的原始 intent。original intent used to find service.
6. final ServiceInfo serviceInfo; //记录了该服务的所有信息,继承于ComponentInfo,包含了进程名、applicationInfo、权限、启动的FLAG等。all information about the service.
7. final ApplicationInfo appInfo; //应用程序信息
8. final String packageName;
9. final String processName;
10. final String permission;// permission needed to access service
11. final String baseDir; 
        ServiceInfo.applicationInfo.sourceDir;
12. final String resDir;
        ServiceInfo.applicationInfo.publicSourceDir;
13. final String dataDir; 
        ServiceInfo.applicationInfo.dataDir;
14. final boolean exported; // from ServiceInfo.exported
15. final Runnable restarter; // used to schedule retries of starting the service
16. final long createTime; //服务创建时间
17. final HashMap<Intent.FilterComparison, IntentBindRecord> bindings
                                    = new HashMap<Intent.FilterComparison, IntentBindRecord>();
        所有激活的binding
18. final HashMap<IBinder, ArrayList<ConnectionRecord>> connections
                                    = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); //所有绑定的Client的连接信息
19. ProcessRecord app; //服务所在进程信息
20. boolean isForeground; //是否是前台服务
21. int foregroundId; //前台服务对应的通知的id
22. Notification foregroundNoti; //前台服务时的通知
23. long lastActivity; // last time there was some activity on the service.
24. boolean startRequested; //该服务是否被startService了
25. boolean stopIfKilled; // last onStart() said to stop if service killed?
26. boolean callStart; // last onStart() has asked to alway be called on restart.
27. int executeNesting; // number of outstanding operations keeping foreground.
28. long executingStart; // start time of last execute request. 最近一次请求开始的时间
29. int crashCount; //服务启动后进程crash的次数
30. int totalRestartCount; // number of times we have had to restart.
31. int restartCount; // number of restarts performed in a row.
32. long restartDelay; // delay until next restart attempt.
33. long restartTime; //上次重启时间
34. long nextRestartTime; // time when restartDelay will expire.
35. private int lastStartId; // identifier of most recent start request.

以上这些字段。

3.2 查询要启动的服务是否为当前运行中的服务

retrieveServiceLocked 方法首先要从当前已经运行的服务中查找是否已经存在该 Intent 对应的 ServiceRecord,若没有再去新建新服务,如下源码所示(frameworks/base/services/java/com/android/server/am/ActivityManagerService.java):

  private ServiceLookupResult retrieveServiceLocked(Intent service,
          String resolvedType, int callingPid, int callingUid) {
      ServiceRecord r = null;
      if (service.getComponent() != null) {
1.        r = mServices.get(service.getComponent());
      }
      Intent.FilterComparison filter = new Intent.FilterComparison(service);
2.    r = mServicesByIntent.get(filter);
      if (r == null) { 
    
          ...... // 在下一小节展开
    
      }
        
      ......
      
  }

这里查找源有两处:mServices 和 mServicesByIntent,mServices 是以 ComponentName 为 Key 的一个 HashMap,Value 类型是 ServiceRecord,mServicesByIntent 则是以 Intent.FilterComparison 为 Key 的 HashMap,Intent.FilterComparison 实际上就是将 Intent 的属性包装了一下,重写 equals、hashcode 方法用于存储和查找。
这段是很奇葩的一个写法,不管你是否在 mServices 中查找到对应 ServiceRecord(步骤1),都会重新在 mServicesByIntent 中再查一次覆盖掉(步骤2),目前还没明白为何这样写,一直觉得应该写成下面这样:

ServiceRecord r = null;
if (service.getComponent() != null) {
    r = mServices.get(service.getComponent());
}
if (r == null) {
    Intent.FilterComparison filter = new Intent.FilterComparison(service);
    r = mServicesByIntent.get(filter);
}

而且后来发现,在 4.1 上就已经改过来了,暂且认为这是 4.0 系统的一个效率问题 bug 吧。

3.3 若非已经运行的服务,则解析 Intent 并创建一个 ServiceRecord

在 mServices 和 mServicesByIntent 中都没有指定服务时,AMS 会创建一个新的 ServiceRecord,这个过程可以分为四个步骤来讲:1. 解析 Intent,2. 构造 ComponentName 再次查找,3. 构造 ServiceRecord,4. 检查该服务对调用者的权限限制。

3.3.1 解析 Intent

if (r == null) {
    try {
        ResolveInfo rInfo =
                AppGlobals.getPackageManager().resolveService(
                        service, resolvedType, STOCK_PM_FLAGS);
        ServiceInfo sInfo =
                rInfo != null ? rInfo.serviceInfo : null;
        if (sInfo == null) {
            Slog.w(TAG, "Unable to start service " + service +
                      ": not found");
            return null;
        }
            
        ...... // 在下一小节展开

    } catch (RemoteException ex) { ...... }
}

ResolveInfo 包含了解析 Intent 获得的服务信息 ServiceInfo 在内的信息,通过 PackageManagerService(以下统称 PMS)提供的公共接口resolveService 对 Intent 解析获取对应的服务(该对外公共接口的实现在此不再赘述)。提上一句,和 queryIntent* 类型的函数不同的是,有些隐式 Intent 若对应多个服务,那么它会只取第一个返回,那么通过隐式 Intent 启动的 Service 其实就是符合 Intent 的服务列表的第一个。
当发现通过该 Intent 没有查找到任何服务组件,则让 retrieveServiceLocked 返回 null。

3.3.2 构造 ComponentName 再次查找

不管是通过 startService 还是 bindService,启动的服务都会存在 AMS 的 mServices 和 mServicesByIntent 字段中,若启动服务的 Intent 为隐式,则 getComponent() 获取为 null(无法在 mServices 中查找),而且很有可能在 mServicesByIntent 中查找不到对应的服务记录,AMS 会根据解析 Intent 查找到的服务组件信息创建一个 ComponentName 再次去 mServices 中查找一次。如下源码:

ComponentName name = new ComponentName(
                    sInfo.applicationInfo.packageName, sInfo.name);
r = mServices.get(name);
if (r == null) { 

    ...... // 在下一小节展开

}

3.3.3 构造 ServiceRecord

若仍然在 mServices 中查不到对应 ServiceRecord,则创建一个新的,新创建的 ServiceRecord 在 mServices 和 mServicesByIntent 中各存一份。这个流程中包括了 BatteryStatsImpl 的创建,BatteryStatsImpl 相关的内容将在电量统计的文章中涉及到,在此不再赘述。源码如下:

if (r == null) {
    filter = new Intent.FilterComparison(service.cloneFilter());
    ServiceRestarter res = new ServiceRestarter();
    BatteryStatsImpl.Uid.Pkg.Serv ss = null;
    BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
    synchronized (stats) {
        ss = stats.getServiceStatsLocked(
                sInfo.applicationInfo.uid, sInfo.packageName,
                sInfo.name);
    }
    r = new ServiceRecord(this, ss, name, filter, sInfo, res);
    res.setService(r);
    mServices.put(name, r);
    mServicesByIntent.put(filter, r);
                
    ...... // 后面展开

}

而后

// Make sure this component isn't in the pending list.
int N = mPendingServices.size();
for (int i=0; i<N; i++) {
    ServiceRecord pr = mPendingServices.get(i);
    if (pr.name.equals(name)) {
        mPendingServices.remove(i);
        i--;
        N--;
    }
}

3.3.4 检查该服务对调用者的权限限制

在查找/创建 ServiceRecord 后,将会分析该服务的权限限制,并判断调用者是否有权限启动该服务,若无权限,retrieveServiceLocked 则返回一个不包含 ServiceRecord 的 ServiceLookupResult 对象,并附上权限说明。源码如下:

if (r != null) {
    if (checkComponentPermission(r.permission,
            callingPid, callingUid, r.appInfo.uid, r.exported)
            != PackageManager.PERMISSION_GRANTED) {
        if (!r.exported) {
            Slog.w(TAG, "Permission Denial: Accessing service " + r.name
                + " from pid=" + callingPid
                + ", uid=" + callingUid
                + " that is not exported from uid " + r.appInfo.uid);
            return new ServiceLookupResult(null, "not exported from uid "
                + r.appInfo.uid);
         }
         Slog.w(TAG, "Permission Denial: Accessing service " + r.name
             + " from pid=" + callingPid
             + ", uid=" + callingUid
             + " requires " + r.permission);
         return new ServiceLookupResult(null, r.permission);
    }
    return new ServiceLookupResult(r, null);
}

checkComponentPermission 的具体细节将在“Android 调用者权限验证”中具体展开。

3.3.5 本节小结

本节主要分析了 retrieveServiceLocked 函数的流程,返回的是包装了 ServiceRecord 和 权限说明的对象 ServiceLookupResult,主要是为了查找传入的 Intent 对应的正在运行或已经安装的服务,查找源有二:AMS 中的 mServices(正在运行中的服务)和 PMS 中的 mServices(已经安装服务的解析类)。

4. bringUpServiceLocked 基本流程

经过第三节的分析,我们知道 AMS 通过 retrieveServiceLocked() 获取指定 Intent 对应的 ServiceRecord,接下来该节将分析如何在新进程中启动的该服务。该过程分三大步:更新 ServiceRecord 的字段、bringUpServiceLocked、startProcessLocked。

4.1 更新 ServiceRecord 的字段

通过下面的代码可以看出首先是要通过 checkGrantUriPermissionFromIntentLocked 方法拿到一个验证权限的结果,该方法实际上去通过验证 Intent 中的 Uri 是否有读写权限来判断的,在这里我们可以忽略这个步骤,然后对 ServiceRecord 的 startRequest、callStart、pendingStarts、lastActivity 分别赋值,最后传入 bringUpServiceLocked 中继续运行。

ComponentName startServiceLocked(IApplicationThread caller,
        Intent service, String resolvedType,
        int callingPid, int callingUid) {
    synchronized(this) {
            
        ......

        ServiceRecord r = res.record;
        int targetPermissionUid = checkGrantUriPermissionFromIntentLocked(
                callingUid, r.packageName, service);
        if (unscheduleServiceRestartLocked(r)) {
            if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r);
        }
        r.startRequested = true;
        r.callStart = false;
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                service, targetPermissionUid));
        r.lastActivity = SystemClock.uptimeMillis();
        synchronized (r.stats.getBatteryStats()) {
            r.stats.startRunningLocked();
        }
        if (!bringUpServiceLocked(r, service.getFlags(), false)) {
            return new ComponentName("!", "Service process is bad");
        }
        return r.name;
    }
}

4.2 执行 bringUpServiceLocked

首先,判断该服务是否已经在运行,如果是已经运行的服务则执行 sendServiceArgsLocked 方法,其会让该服务执行 onStartCommand,sendServiceArgsLocked 方法在这里具体先不展开:

private final boolean bringUpServiceLocked(ServiceRecord r,
        int intentFlags, boolean whileRestarting) {
    //Slog.i(TAG, "Bring up service:");
    //r.dump("  ");

    if (r.app != null && r.app.thread != null) {
        sendServiceArgsLocked(r, false);
        return true;
    }

    if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent);

    // We are now bringing the service up, so no longer in the
    // restarting state.
    mRestartingServices.remove(r);

    ......

}

上面的代码最后一行 mRestartingServices.remove(r) 是将要启动的服务从“被系统干掉或者自己crash后即将自己重启的服务列表”中移除。

如果服务未在运行,则会启动该服务,在此之前需要确定该服务运行在哪个目标进程,如果目标进程已经存在,则直接在该进程中运行服务,如下代码所示,realStartServiceLocked 为启动该服务的真正方法:

private final boolean bringUpServiceLocked(ServiceRecord r,
        int intentFlags, boolean whileRestarting) {
    
    ......

    final String appName = r.processName;
    ProcessRecord app = getProcessRecordLocked(appName, r.appInfo.uid);
    if (app != null && app.thread != null) {
        try {
            app.addPackage(r.appInfo.packageName);
            realStartServiceLocked(r, app);
            return true;
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception when starting service " + r.shortName, e);
        }

        // If a dead object exception was thrown -- fall through to
        // restart the application.
    }

    ......

}

如果目标进程未再运行,则会启动一个新的进程,然后在此进程中运行服务:

private final boolean bringUpServiceLocked(ServiceRecord r,
        int intentFlags, boolean whileRestarting) {
    
    ......

    // Not running -- get it started, and enqueue this service record
    // to be executed when the app comes up.
    if (startProcessLocked(appName, r.appInfo, true, intentFlags,
            "service", r.name, false) == null) {
        Slog.w(TAG, "Unable to launch app "
                + r.appInfo.packageName + "/"
                + r.appInfo.uid + " for service "
                + r.intent.getIntent() + ": process is bad");
        bringDownServiceLocked(r, true);
        return false;
    }
    
    if (!mPendingServices.contains(r)) {
        mPendingServices.add(r);
    }

    ......

}

4.3 执行 startProcessLocked

在函数 startProcessLocked 中,先根据传进来的进程名、应用信息创建一个 ProcessRecord,然后重载的另一个 startProcessLocked 启动进程:

final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName, boolean allowWhileBooting) {
    
    ......
    
    if (app == null) {
        app = newProcessRecordLocked(null, info, processName);
        mProcessNames.put(processName, info.uid, app);
    } else {
        // If this is a new package in the process, add the package to the list
        app.addPackage(info.packageName);
    }

    ......

    startProcessLocked(app, hostingType, hostingNameStr);
    return (app.pid != 0) ? app : null;
}

在重载的 startProcessLocked 中,会根据初始化好的 uid、processName、gids、debugflags 使用 Process.start 启动指定进程,然后在 mPidsSelfLocked 中插入对应的 ProcessRecord,同时也会有超时判断与处理:

private final void startProcessLocked(ProcessRecord app,
        String hostingType, String hostingNameStr) {

    ...... // 初始化 uid、gids、debugflags

    // Start the process.  It will either succeed and return a result containing
    // the PID of the new process, or else throw a RuntimeException.
    Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
                app.processName, uid, uid, gids, debugFlags,
                app.info.targetSdkVersion, null);

    BatteryStatsImpl bs = app.batteryStats.getBatteryStats();
    synchronized (bs) {
        if (bs.isOnBattery()) {
            app.batteryStats.incStartsLocked();
        }
    }
        
    ...... // log信息

    synchronized (mPidsSelfLocked) {
            this.mPidsSelfLocked.put(startResult.pid, app);
            Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
            msg.obj = app;
            mHandler.sendMessageDelayed(msg, startResult.usingWrapper
                    ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
    }

}

可以看出,Process.start(通过 Zygote)启动的实际上就是"android.app.ActivityThread"这个入口类。

4.4 ActivityThread.main

作为一个 java 入口类,ActivityThread 自然有自己的 main 函数,该函数一共做了两件事:1. ActivityThread.attach,2. Looper.loop(这也就是每个 android 应用程序的进程中主线程的 Looper 了)。ActivityThread 的 main 函数如下代码段所示:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = new Handler();
    }

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

此时已经在创建的新进程上下文中了,我们在下一篇文章中来分析在该新进程中,是如何搞定 Application 的初始化和 Service 的启动的。

标签: none

添加新评论