我们在第一篇文章Android P APP冷启动过程全解析(之一)中讲述
了APP冷启动的请求阶段,这一篇主要讲述进程启动阶段,包括以下子阶段:
7.AMS请求创建进程
8.Zygote fork进程
9.初始化 Runtime
10.注册进程到system_server
11.创建application
下面我们来逐个分析:
7、AMS请求创建进程
在resume目标activity的过程中,如果目标activity所在的进程还没启动,那么AMS会通过startSpecificActivityLocked来启动进程:
ASS.startSpecificActivityLocked(...)
// 再次查找进程有没有启动
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
if (app != null && app.thread != null) {
realStartActivityLocked(...); // 这里会真正地startActivity
}
AMS.startProcessLocked(r.processName,...)
app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
AMS.startProcessLocked(app,hostingType,...)
// 新进程的进入java世界的入口类
final String entryPoint = "android.app.ActivityThread";
AMS.startProcess(...)
Process.start(entryPoint,...)
ZygoteProcess.start(...)
ZygoteProcess.startViaZygote(...)
zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote)
AMS.handleProcessStartedLocked(app,...)
整个过程并不是很复杂,首先AMS会创建进程对应的实体ProcessRecord,然后指定新进程起来以后进入java世界的入口点:
final String entryPoint = "android.app.ActivityThread";
即android.app.ActivityThread类,当然还有很多其他的参数,例如processName,uid,targetSdkVersion等。
然后AMS委托Process类来新建进程,而最后干活的是ZygoteProcess,这个类维护着AMS和Zygote进程之间的通讯,通讯的方式是LocalSocket。
最后,ZygoteProcess会把所有启动参数都通过socket的写入流发送给Zygote。这样子,一个创建进程的请求就已经成功发送了。理论上,只要知道Zygote的本地地址,APP也是可以通过向Zygote发送请求创建进程的。
请求发送以后,ZygoteProcess会马上从socket中读int类型的数据,这个int类型的数据便是Zygote返回的新建进程的pid,只有pid>0,那么就可以认为新进程创建成功了!
之后,AMS会调用handleProcessStartedLocked来处理后面的事情,并添加记录,等待新进程起来以后attachApplication。
8、Zygote fork进程
Zygote,含义为受精卵,是人的第一个细胞,其他细胞都是由其分裂出来的。对于Android,它是第一个java进程,其他所有Java都是由其fork出来的。Java进程也是普通的Linux进程,只不过它运行着Java虚拟机,所以称为Java进程。一般来说fork都是Linux进程的行为,Java进程是不会调用fork的,Zygote进程起来的时候,已经启动了ART虚拟机,其他参数也初始化好了。Android用这种fork的方式,克隆出一个和原来进程几乎完全相同的进程,新进程不用在进行初始化操作,只需要修改一些关键参数就可以了,这样子就极大地加快了新建进程的速度。
Zygote进程有init进程fork出来,在其初始化了以后,会运行runSelectLoop来等待客户端进行socket连接,如下:
ZygoteServer.runSelectLoop:
while (true) { // 死循环
ZygoteConnection connection = acceptCommandPeer(abiList); // 获取连接
final Runnable command = connection.processOneCommand(this); // 执行fork请求
if (mIsForkChild) {
return command; //只有子进程才会返回
} else {
connection.closeSocket(); // 关闭连接,等待下一个请求
} finally {
mIsForkChild = false; // 重置标志
}
}
ZygoteConnection.processOneCommand:
args = readArgumentList(); // 读取请求方传过来的参数
parsedArgs = new Arguments(args); // 解析参数
pid = Zygote.forkAndSpecialize(...)
if (pid == 0) {
// 子进程
zygoteServer.setForkChild(); // 就是将mIsForkChild置为true
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
return handleChildProc(...) // 返回command
} else {
...
return null; // 返回null
}
注:上面代码是伪代码(其实本人所以贴的代码都是伪代码~)。
runSelectLoop是一个死循环,ZygoteServer在这个循环中一直等待客户端连接,调用acceptCommandPeer获取连接,然后通过processOneCommand进行fork。processOneCommand函数调用native方法fork出一个子进程,父进程和子进程会分两次返回。pid = 0 说明是子进程,将mIsForkChild置为true,然后调用handleChildProc进行初始化,否则是父进程。最终子进程会返回一个Runnable,该Runable的作用就是反射调用android.app.ActivityThread的main方法。
9、初始化 Runtime
接上面,子进程被fork出来以后,首先调用了handleChildProc来进行初始化:
ZygoteConnection.handleChildProc(...)
Process.setArgV0(parsedArgs.niceName);
ZygoteInit.zygoteInit(...)
RuntimeInit.commonInit();
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(...))
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader)
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
findStaticMain(args.startClass,) // 反射查找main方法
handleChildProc函数里面进行了一些最基本的初始化操作,包括:
设置UncaughtExceptionHandler,默认是KillApplicationHandler,即默认情况下未捕捉的异常会使我们的APP挂掉
通过setTargetHeapUtilization设置Java虚拟机的堆内存利用率的百分比,当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢 。默认值是0.75。
设置targetSdkVersion,这个值会限制我们对某些API的调用。
此外还有一些其他的初始化,在这之后,会通过findStaticMain来发射查找main方法,封装成runnable返回并最终被调用。
反射后会进入到ActivityThread的main函数中,继续做一些初始化工作:
ActivityThread.main(String[] args)
Environment.initForCurrentUser(); // 初始化用户环境
Looper.prepareMainLooper(); // 准备MainLooper
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);//告诉AMS我已经起来啦,startSeq是标识
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited"); // 永远不应该走到这里
ActivityThread的main函数主要做了两件事:
调用attach,告诉AMS进程应启动完毕,可以进行其他事情了。
初始化主线程的looper并loop。
这里重点讲第2点,第1点在下一节讲述。我们都知道主线程是默认实现了looper的,就是这个时候实现的。调用了Looper.loop()后,整个线程就陷入死循环,常规的调用流程已经结束了。后面主线程所作的任何事情都是通过往MainThreadHandler中发送消息来完成的,所以说Android系统是基于消息驱动的。
10、注册进程到system_server
上面说过,这个过程是通过ActivityThread.attah函数来完成的,如下:
ActivityThread.attah(...)
final IActivityManager mgr = ActivityManager.getService();
mgr.attachApplication(mAppThread, startSeq);
AMS.attachApplication(...)
AMS.attachApplicationLocked(...)
final ProcessRecord pending = mPendingStarts.get(startSeq);
AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread); // 绑定死亡通知
thread.asBinder().linkToDeath(adr, 0);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); // 移除进程启动超时消息
ApplicationThread.bindApplication(..)
ASS.attachApplicationLocked(...) // 检查有没有待启动的activity
ActiveServices.attachApplicationLocked(...) // 检查有没有待启动的service
AMS.sendPendingBroadcastsLocked // 检查有没有待启动的receiver
因为进程一般都是由AMS请求的,所以这里相当于一个回调,为了防止其他进程非法attach到AMS,保证安全,需要验证身份。这个验证就是通过startSeq来完成的,这个startSeq其实是由AMS传过去的,现在它在Zygote和APP进程中转了一圈又回来了。AMS会根据startSeq从mPendingStarts取出待启动的进程的ProcessRecord ,如果ProcessRecord 为空,那么说明进程启动有误或者这个进程不是由AMS启动的,AMS会马上杀死该进程。
在验证进程启动没问题之后,AMS会做一下3件事情:
为进程绑定死亡通知AppDeathRecipient,这样,进程被杀的时候,AMS会通过AppDeathRecipient来进行清理工作。
通知APP执行bindApplication,即创建Application,这个会在下一节中讲述
检查有没有四大组件等待着在该进程中运行,如果有,继续执行四大组件。不过这里只有Activity、Service和BroadCastReceiver三种组件,ContentProvider的发布会在bindApplication的时候进行。
11、创建application
上一节说到,进程启动没问题以后,AMS会通知APP创建Appication,主要涉及到的函数是ActivityThread.handleBindApplication:
Process.setStartTimes(...)
mConfiguration = new Configuration(data.config);
//获取该应用的packageInfo
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
ImageDecoder.sApiLevel = data.appInfo.targetSdkVersion;
//dpi相关
mCurDefaultDisplayDpi = data.config.densityDpi;
Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
// 设置时间格式
DateFormat.set24HourTimePref(is24Hr);
// 设置strictMode相关的东西
StrictMode.initThreadDefaults(data.appInfo);
penaltyDeathOnNetwork(...)
StrictMode.initVmDefaults(data.appInfo);
penaltyDeathOnFileUriExposure(...)
app = data.info.makeApplication(...)
installContentProviders(app, data.providers); // 发布所有ContentProvider
mInstrumentation.callApplicationOnCreate(app); // 回调Application.onCreate
创建application的过程主要是初始化APP的Context,各种资源的初始化,包括设定显示相关的configuration、初始化packageInfo、设置默认的Dpi、设置时间格式、设置strictMode,设置字体资源等等,为以后四大组件的运行提供运行环境。
其中packageInfo包括app的所有资源,例如data目录,res的目录,lib目录的。而strictMode会设定不能在主线程中访问网络,也不能在Uri中显式加入file。然后创建Application,用它去发布所有的ContentProvider,才回调Application.onCreate,所以不要惊讶,ContentProvider发布是比Application的回调都要早。至此,APP的运行所需要的所有资源已经准备完毕,
四大组件可以运行了。
第二阶段总结
第二阶段主要是通过Zygotefork出来APP运行的实体——进程,然后初始化APP运行的环境好上下文,准备好各种资源,为四大组件的运行做好准备。从某种意义上来说,进程也不过是四大组件运行的必须资源罢了。