声明:
一个apk进程包括两种资源,一种是进程本身运行实体,另一种就是系统管理进程的进程记录表(ProcessRecord),进程的启动和回收就是申请和释放以上两种资源,对第一种资源的申请过程我们定义为launch进程,对第二种资源的申请过程我们定义为new进程记录表,进程记录表(ProcessRecord)是系统对apk进程的详细描述记录,基于复用原则,进程记录表的生命周期是大于等于进程实体的生命周期的,理解这一点很重要。

apk进程的启动

android定义了两种apk进程,一种是常驻内存的(从开机到关机是一直存在的),另一种是当有组件需要在这个进程中运行的时候,才拉起这个进程;基于内存的考虑,大部分都是第二种的,第一种常驻内存的都是一些非常重要的系统apk进程;对于第一种进程我们定义为persistent进程, 第二种我们定义为general进程。
对这两种进程的启动,AMS定义了两组方法:

  • 1.第一个方法直接调用第二个方法,将第二个方法的后四个参数设置为null,前面的参数一模一样,所以我们只关注第二个方法(isolated进程后续单独讲解),这两个方法我们定义为启动进程
  • ProcessRecord startProcessLocked(String processName,
    ApplicationInfo info, boolean knownToBeDead, int intentFlags,
    String hostingType, ComponentName hostingName, boolean allowWhileBooting,
    boolean isolated, boolean keepIfLarge)
  • ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
    boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName,
    boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
    String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler)

参数列表:
1.processName:进程名字
2.info:AndroidManifest.xml中定义的Application的信息
3.knownToBeDead:activity、service、广播触发的流程这个值为true
4.intentFlags:启动进程的过程是由启动组件触发的,这个intent 标志就是启动组件的标志(再加上一些自己的标志)
5.hostingType:启动进程的过程是由启动组件触发的,这个就是由指哪一个类型组件触发的, 启动activity,这个值就是“activity”, 启动service这个值就是“service”,启动contentprovider这个值就是”content provider”,启动广播接收器这个值就是”broadcast”,启动backup这个值就是”backup”
6.hostingName:启动进程的过程是由启动组件触发的,这个值就是指启动组件的信息
7.allowWhileBooting:允许在系统正在启动(booting)的时候启动这个进程,否则的话挂起这个进程,系统启动完成时在触发启动进程,五大组件(四大组件和backup组件)触发的进程启动中,只有带FLAG_RECEIVER_BOOT_UPGRADE的广播触发的才允许在系统启动的时候启动进程,其他的不可以,都是暂时挂起
8.isolated:isolated进程
9.isolatedUid:isolated进程 uid
10.keepIfLarge:足够大就保持,什么足够大?当然是重要性,五大组件最重要的是什么?Activity,因为Activity是用户可见的,保持什么?保持在内存,所以这变量和内存策略有关,这个值只有由启动Activity的流程触发的时候为true
11.abiOverride:ABI覆盖值,启动普通进程这个值为null,isolated进程外部指定
12.entryPoint:java入口,启动普通进程这个值为null(内部流程直接指定为ActivityThread),isolated进程外部指定
13.entryPointArgs:java入口参数列表,启动普通进程这个值为null,isolated进程外部指定
14.crashHandler:启动普通进程这个值为null,isolated进程外部指定

  • 2.这个方法我们定义为添加程序
  • ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
    String abiOverride)
  • 启动进程(startProcessLocked)和添加程序(addAppLocked)这两个方法有什么区别呢?
    启动进程(startProcessLocked)是new进程记录表和launch进程这两个过程都需要运行,说明进程马上就可以使用,apk进程可以马上运行组件了,所以启动进程(startProcessLocked)是四大组件(加上backup,应该是五大组件)启动流程触发执行的,触发的方法如下,基于以上特性,这个方法只作用于general进程:
  • 1.activity组件:ActivityStackSupervisor::startSpecificActivityLocked()
  • 2.service组件:ActiveServices::bringUpServiceLocked()
  • 3.ContentProvider组件:ActivityManagerService::getContentProviderImpl()
  • 4.广播接收器组件:BroadcastQueue::processNextBroadcast()
  • 5.backup组件:ActivityManagerService::bindBackupAgent()

添加程序(addAppLocked)是new进程记录表这个过程需要执行,但是launch进程这个过程不一定执行,也就是说apk进程还不一定可以使用,分两种情况:

  • 1.general进程:
    这种进程在需要运行组件的时候,进程实体才需要存在,所以添加程序(addAppLocked)不会调用launch进程这个过程
  • 2.persistent进程:
    这种进程无论是不是空,进程实体都是存在,所以添加程序(addAppLocked)会调用launch进程这个过程

启动进程(startProcessLocked)执行流:

  • 1.查询进程记录表
    如果进程记录表的pid>0,证明进程记录表有效,检查是否进程已经在启动,如果已经在启动的话,添加package信息,直接返回,否则的话,调用handleAppDiedLocked()复位进程记录表
  • 2.如果进程记录表不存在,调用newProcessRecordLocked()创建进程记录表,如果存在,直接添加package的信息
  • 3.如果不允许启动,添加到进程挂起容器(mProcessesOnHold),直接返回
  • 4.launch进程
  • 5.返回进程记录表,启动进程第一个执行流到此结束

添加程序(addAppLocked)执行流:

  • 1.查询进程记录表,如果不存在的话,调用newProcessRecordLocked()创建进程记录表
  • 2.如果是persistent进程并且没有launch进程,开始launch进程;如果是general进程,直接返回进程记录表

使用哪个方法来执行进程的启动,有一个基本原则:那就是进程是不是需要立马运行组件,本小节所有的讨论一律不考虑isolated,以后单独讲解

launch进程框图

launch进程涉及到三个执行流(算上组件的启动,应该是四个独立的执行流)

  • 1.第一个执行流: 在system_server进程中,发送孵化apk进程的请求给zygote(触发第二个执行流),然后等待zygote进程的结果,以返回pid结束当前执行流
  • 2.第二个执行流:zygote接收请求孵化apk进程,apk进程调用ActivityThread::main()方法,在这个主方法中将自己attach到ActivityManagerService中,AMS执行连接应用的操作,在连接应用的操作中发送两个异步消息给apk进程:
  • 绑定应用的异步消息给apk进程(触发第三个执行流)
  • 检查需要启动的组件,发送启动组件的异步消息给apk进程(触发第四个执行流)

最后attach调用返回,主线程调用looper.loop()开始处理异步消息,如果没有消息,主线程(UI线程)睡眠等待。

  • 3.第三个执行流:UI 线程处理绑定应用的消息
  • 4.第四个执行流:UI 线程处理拉起组件的消息

下面先上一个草图养养眼:

android平台下实现一个进程管理器 android 进程管理app_android

很重要的一句话:当第一个执行流执行完,第二个执行流正在执行的时候,第一个执行流有可能再次被触发执行,有人可能会觉得,AMS执行startProcess()和attachApplication()都是加锁执行的,以上情况不会出现,那么如果第二个执行流执行attach()方法的时候,还没有执行到mgr.attachApplication(mAppThread)方法呢?由于内核的进程调度,这种情况是有可能发生的

apk进程启动执行流详细过程

第一个执行流:

launch进程(声明中提到的对第一种资源的申请)是由如下方法实现的:

  • void startProcessLocked(ProcessRecord app, String hostingType,
    String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs)

参数说明:
1. app:进程记录表
2. hostingType:触发类型,除了上面提到的五大组件的触发类型外,还增加了如下类型:”link fail”、”bind fail”、”on-hold”、”restart”、”added application”;
3. hostingNameStr:五大组件启动触发的为组件信息; “link fail”、”bind fail”、”restart”、”added application”是process name; “on-hold” 为null
4. abiOverride :abi覆盖
5. entryPoint :java入口
6. entryPointArgs :java入口参数列表

下面我们重点说明一下hostingType增加的类型(1.2.3.4小知识点中提到的方法全是属于ActivityManagerService):

  • 1.其中前两个是下面将要讲解的第二个执行流触发的,第二个执行流中标示为3的步骤如果出现异常的话,会以”link fail”类型重启进程记录表,标示为6的步骤如果出现异常的话,会以”bind fail”类型重启进程记录表,这个比较好理解,因为系统是尽量满足创建apk的请求的;
  • 2.”on-hold”类型是启动挂起的进程记录表,这个进程记录表在系统启动的时候直接挂起,系统启动完成再触发启动,所以触发的方法就是finishBooting();
  • 3.”restart”类型是进程重启,也就是说系统回收完进程资源再马上重新启动这个进程记录表,所以触发的方法就是cleanUpApplicationRecordLocked();
  • 4.”added application”这个类型是由addAppLocked()触发的,启动persistent进程的一部分执行流。

参数介绍完毕,下面介绍具体的流程:

  • 1.首先检查pid map,如果存在的话,需要清除
  • 2.从挂起容器中清除
  • 3.调用updateCpuStats()更新cpu状态
  • 4.启动package
  • 5.计算uid gid debug标志等等需要传给zygote进程的数据
  • 6.设置默认的java入口(”android.app.ActivityThread”)
  • 7.调用Process.start()发送数据给zygote进程,请求zygote帮忙创建apk进程
  • 8.挂起在socket文件,等待zygote进程发送回pid,
  • 9.添加进程记录表到pid map

第二个执行流

第二个执行流是apk进程开始执行的,主线程调用ActivityThread::attach()方法,这个方法最重要的作用就是将ApplicationThread的客户端(起个名字就叫进程记录表的连接器)链接到AMS(客户端怎么在binder上传输的,属于binder传输的知识,建议大家先了解binder数据的传输机制),调用的方法是AMS的attachApplication(),所以这个执行流只要分析这个方法

参数列表
这个方法只有一个参数,IApplicationThread:ApplicationThread的客户端

执行流程如下:

  • 1.检查pid map(apk进程pid是binder直接传到AMS的),如果map中不存在,直接kill掉,返回false,因为如果map中不存在,证明这个进程不是AMS启动的,AMS不会为这种进程提供服务
  • 2.如果进程记录表的连接器存在的话,说明这个进程记录表有一个连接进程,这个时候需要调用handleAppDiedLocked()回收这个进程,然后进程记录表才可以使用
  • 3.注册一个死亡讣告,如果apk进程异常挂掉的话,AMS会收到消息,回收系统资源
  • 4.初始化进程记录表数据
  • 5.查询进程所有需要安装的ContentProvider,不是package,是所有将要在这个进程中运行的package
  • 6.发送bindApplication消息给apk进程(触发第三个执行流,这个时候第三个执行流还没有执行,因为当前执行流还在占用主线程,现在apk进程只是将这个消息插入消息队列)
  • 7.检查五大组件是否有需要执行的,如果有的话,发送拉起消息给apk进程(触发第四个执行流,这个时候第三个执行流还没有执行,因为当前执行流还在占用主线程,现在apk进程只是将这个消息插入消息队列)
  • 8.返回true,第二个执行流结束

第三个执行流

当第二个执行流结束时,主线程会开始循环处理消息队列中的消息,如果没有消息的话,就睡眠等待,目前来看,当第二个执行流结束时,至少有两个消息,第一个是触发第三个执行流的bindApplication消息,第二个是触发拉起组件的消息
下面先来分析bindApplication的消息,这个消息的处理方法是ActivityThread::handleBindApplication()

参数介绍
这个方法的参数是AppBindData对象,这个对象是对AMS发送过来的数据的简单打包,我们简单看一下AMS都发送了哪些数据给UI 线程
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;//这个比较重要,这个是需要安装的所有contentprovider
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableBinderTracking = enableBinderTracking;
data.trackAllocation = trackAllocation;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
sendMessage(H.BIND_APPLICATION, data);

绑定应用的处理流程基本没有什么复杂的逻辑,最主要的两件事一是加载Application并且调用onCreate()方法,另一件事就是安装所有的ContentProvider,然后发布(publish)客户端到系统

第四个执行流

拉起组件的消息的处理方法如下,在这边文章中我主要分析进程,涉及到的组件的管理在后续文章中会有专门的分析:

  • 1.拉起activity的方法是:handleLaunchActivity()
  • 2.拉起service的方法是:handleCreateService()
  • 3.拉起广播接收器的方法是:handleReceiver()
  • 4.拉起contentprovider的方法是:handleInstallProvider()
  • 5.拉起backup的方法是:handleCreateBackupAgent()