图解 | 一图摸清 Android 应用进程的启动

用户头像
哈利迪
关注
发布于: 2020 年 10 月 25 日
图解 | 一图摸清Android应用进程的启动

一图摸清Android应用进程的启动~



大纲:



  • 简要回顾

  • AMS发送socket请求

  • Zygote处理socket请求

  • 启动binder线程池

  • 总结

  • 细节补充

  • 参考资料



本文约2.5k字,阅读大约11分钟。

>

Android源码基于8.0。



简要回顾



先回顾一下Android系统的启动过程:



init进程fork出Zygote进程后,Zygote进程会创建一个服务端socket,等待AMS发起socket请求。



同时,由Zygote进程fork出的SystemServer进程会启动各项系统服务,其中就包含了AMS,AMS会启动Launcher桌面,此时就可以等待用户点击App图标来启动应用进程了。





然后看下系统服务的启动,不管是由init进程启动的独立进程的系统服务如SurfaceFlinger,还是由SystemServer进程启动的非独立进程的系统服务如AMS,都是在ServiceManager进程中完成注册和获取的,在跨进程通信上使用了Android的binder机制。





ServiceManager进程本身也是一个系统服务,经过启动进程、*启动binder机制*、发布自己和*等待请求*4个步骤,就可以处理其他系统服务的获取和注册需求了。



AMS发送socket请求



Android应用进程的启动是被动式的,在Launcher桌面点击图标启动一个应用的组件如Activity时,如果Activity所在的进程不存在,就会创建并启动进程。



点击App图标后经过层层调用会来到ActivityStackSupervisor的startSpecificActivityLocked方法,



//ActivityStackSupervisor.java
final ActivityManagerService mService;
void startSpecificActivityLocked(...) {
//查找Activity所在的进程,ProcessRecord是用来封装进程信息的数据结构
ProcessRecord app = mService.getProcessRecordLocked(...);
//如果进程已启动,并且binder句柄IApplicationThread也拿到了,那就直接启动Activity
if (app != null && app.thread != null) {
realStartActivityLocked(r, app, andResume, checkConfig);
return;
}
//否则,让AMS启动进程
mService.startProcessLocked(...);
}



app.thread并不是线程,而是一个binder句柄。应用进程使用AMS需要拿到AMS的句柄IActivityManager,而系统需要通知应用和管理应用的生命周期,所以也需要持有应用进程的binder句柄IApplicationThread。



也就是说,他们互相持有彼此的binder句柄,来实现双向通信





那IApplicationThread句柄是怎么传给AMS的呢?



Zygote进程收到socket请求后会处理请求参数,执行ActivityThread的入口函数main,



//ActivityThread.java
public static void main(String[] args) {
//创建主线程的looper
Looper.prepareMainLooper();
//ActivityThread并不是线程,只是普通的java对象
ActivityThread thread = new ActivityThread();
//告诉AMS,应用已经启动好了
thread.attach(false);
//运行looper,启动消息循环
Looper.loop();
}
private void attach(boolean system) {
//获取AMS的binder句柄IActivityManager
final IActivityManager mgr = ActivityManager.getService();
//告诉AMS应用进程已经启动,并传入应用进程自己的binder句柄IApplicationThread
mgr.attachApplication(mAppThread);
}



所以对于AMS来说,



  1. AMS向Zygote发起启动应用的socket请求,Zygote收到请求fork出进程,返回进程的pid给AMS;

  2. 应用进程启动好后,执行入口main函数,通过attachApplication方法告诉AMS已经启动,同时传入应用进程的binder句柄IApplicationThread。



完成这两步,应用进程的启动过程才算完成。



下面看AMS的startProcessLocked启动应用进程时都做了些什么。



//ActivityManagerService.java
final ProcessRecord startProcessLocked(...){
ProcessRecord app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
//如果进程信息不为空,并且已经拿到了Zygote进程返回的应用进程pid
//说明AMS已经请求过了,并且Zygote已经响应请求然后fork出进程了
if (app != null && app.pid > 0) {
//但是app.thread还是空,说明应用进程还没来得及注册自己的binder句柄给AMS
//即此时进程正在启动,那就直接返回,避免重复创建
if (app.thread == null) {
return app;
}
}
//调用重载方法
startProcessLocked(...);
}



之所以要判断app.thread,是为了避免当应用进程正在启动的时候,假如又有另一个组件需要启动,导致重复拉起(创建)应用进程。



继续看重载方法startProcessLocked,



//ActivityManagerService.java
private final void startProcessLocked(...){
//应用进程的主线程的类名
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
ProcessStartResult startResult = Process.start(entryPoint, ...);
}
//Process.java
public static final ProcessStartResult start(...){
return zygoteProcess.start(...);
}



来到ZygoteProcess,



//ZygoteProcess.java
public final Process.ProcessStartResult start(...){
return startViaZygote(...);
}
private Process.ProcessStartResult startViaZygote(...){
ArrayList<String> argsForZygote = new ArrayList<String>();
//...处理各种参数
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}



其中:



  1. openZygoteSocketIfNeeded打开本地socket

  2. zygoteSendArgsAndGetResult发送请求参数,其中带上了ActivityThread类名

  3. return返回的数据结构ProcessStartResult中会有pid字段



梳理一下:





注意:Zygote进程启动时已经创建好了虚拟机实例,所以由他fork出的应用进程可以直接继承过来用而无需创建。



下面来看Zygote是如何处理socket请求的。



Zygote处理socket请求



图解Android系统的启动 一文可知,在ZygoteInit的main函数中,会创建服务端socket,



//ZygoteInit.java
public static void main(String argv[]) {
//Server类,封装了socket
ZygoteServer zygoteServer = new ZygoteServer();
//创建服务端socket,名字为socketName即zygote
zygoteServer.registerServerSocket(socketName);
//进入死循环,等待AMS发请求过来
zygoteServer.runSelectLoop(abiList);
}



看到ZygoteServer,



//ZygoteServer.java
void registerServerSocket(String socketName) {
int fileDesc;
//socket真正的名字被加了个前缀,即 "ANDROID_SOCKET_" + "zygote"
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
//创建文件描述符fd
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);
//创建LocalServerSocket对象
mServerSocket = new LocalServerSocket(fd);
}
void runSelectLoop(String abiList){
//进入死循环
while (true) {
for (int i = pollFds.length - 1; i >= 0; --i) {
if (i == 0) {
//...
} else {
//得到一个连接对象ZygoteConnection,调用他的runOnce
boolean done = peers.get(i).runOnce(this);
}
}
}
}



来到ZygoteConnection的runOnce,



//ZygoteConnection.java
boolean runOnce(ZygoteServer zygoteServer){
//读取socket请求的参数列表
String args[] = readArgumentList();
//创建应用进程
int pid = Zygote.forkAndSpecialize(...);
if (pid == 0) {
//如果是应用进程(Zygote fork出来的子进程),处理请求参数
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
return true;
} else {
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
}



handleChildProc方法调用了ZygoteInit的zygoteInit方法,里边主要做了3件事:



  1. 启动binder线程池(后面分析)

  2. 读取请求参数拿到ActivityThread类并执行他的main函数,执行thread.attach告知AMS并回传自己的binder句柄

  3. 执行Looper.loop()启动消息循环(代码前面有)



这样应用进程就启动起来了。梳理一下,





下面看下binder线程池是怎么启动的。



启动binder线程池



Zygote的跨进程通信没有使用binder,而是socket,所以应用进程的binder机制不是继承而来,而是进程创建后自己启动的。



前边可知,Zygote收到socket请求后会得到一个ZygoteConnection,他的runOnce会调用handleChildProc,



//ZygoteConnection.java
private void handleChildProc(...){
ZygoteInit.zygoteInit(...);
}
//ZygoteInit.java
public static final void zygoteInit(...){
RuntimeInit.commonInit();
//进入native层
ZygoteInit.nativeZygoteInit();
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}



来到AndroidRuntime.cpp



//AndroidRuntime.cpp
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz){
gCurRuntime->onZygoteInit();
}



来到app_main.cpp



//app_main.cpp
virtual void onZygoteInit()
{
//获取单例
sp<ProcessState> proc = ProcessState::self();
//在这里启动了binder线程池
proc->startThreadPool();
}



看下ProcessState.cpp



//ProcessState.cpp
sp<ProcessState> ProcessState::self()
{
//单例模式,返回ProcessState对象
if (gProcess != NULL) {
return gProcess;
}
gProcess = new ProcessState("/dev/binder");
return gProcess;
}
//ProcessState构造函数
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver)) //打开binder驱动
,//...
{
if (mDriverFD >= 0) {
//mmap是一种内存映射文件的方法,把mDriverFD映射到当前的内存空间
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ,
MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}
}
//启动了binder线程池
void ProcessState::startThreadPool()
{
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
//创建线程名字"Binder:${pid}_${自增数字}"
String8 name = makeBinderThreadName();
sp<Thread> t = new PoolThread(isMain);
//运行binder线程
t->run(name.string());
}
}



ProcessState有两个宏定义值得注意一下,感兴趣可以看 一次Binder通信最大可以传输多大的数据 这篇文章,



//ProcessState.cpp
//一次Binder通信最大可以传输的大小是 1MB-4KB*2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
//binder驱动的文件描述符fd被限制了最大线程数15
#define DEFAULT_MAX_BINDER_THREADS 15



我们看下binder线程PoolThread长啥样,



class PoolThread : public Thread
{
public:
explicit PoolThread(bool isMain)
: mIsMain(isMain){}
protected:
virtual bool threadLoop()
{ //把binder线程注册进binder驱动程序的线程池中
IPCThreadState::self()->joinThreadPool(mIsMain);
return false;
}
const bool mIsMain;
};



来到IPCThreadState.cpp



//IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{
//向binder驱动写数据:进入死循环
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
status_t result;
do {
//进入死循环,等待指令的到来
result = getAndExecuteCommand();
} while (result != -ECONNREFUSED && result != -EBADF);
//向binder驱动写数据:退出死循环
mOut.writeInt32(BC_EXIT_LOOPER);
}
status_t IPCThreadState::getAndExecuteCommand()
{
//从binder驱动读数据,得到指令
cmd = mIn.readInt32();
//执行指令
result = executeCommand(cmd);
return result;
}



梳理一下binder的启动过程:



  1. 打开binder驱动

  2. 映射内存,分配缓冲区

  3. 运行binder线程,进入死循环,等待指令



总结



综上,Android应用进程的启动可以总结成以下步骤:



  1. 点击Launcher桌面的App图标

  2. AMS发起socket请求

  3. Zygote进程接收请求并处理参数

  4. Zygote进程fork出应用进程,应用进程继承得到虚拟机实例

  5. 应用进程启动binder线程池、运行ActivityThread类的main函数、启动Looper循环



完整流程图:





可见binder用得还是非常多的,下篇就补一补binder吧~



系列文章:





细节补充



  • 抛异常清空堆栈帧:Zygote不是直接执行ActivityThread的main函数的,而是通过抛出一个异常进行捕获,捕获后再执行,这样可以清除初始化过程产生的调用堆栈,让ActivityThread的main函数看起来像个应用程序进程的入口函数。



参考资料








更多性感文章,关注原创技术公众号:哈利迪ei



发布于: 2020 年 10 月 25 日 阅读数: 9
用户头像

哈利迪

关注

公众号:哈利迪ei,技术从未如此性感 2019.02.13 加入

还未添加个人简介

评论

发布
暂无评论
图解 | 一图摸清Android应用进程的启动