一图摸清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方法,
1 | //ActivityStackSupervisor.java |
app.thread并不是线程,而是一个binder句柄。应用进程使用AMS需要拿到AMS的句柄IActivityManager,而系统需要通知应用和管理应用的生命周期,所以也需要持有应用进程的binder句柄IApplicationThread。
也就是说,他们互相持有彼此的binder句柄,来实现双向通信。
那IApplicationThread句柄是怎么传给AMS的呢?
Zygote进程收到socket请求后会处理请求参数,执行ActivityThread的入口函数main,
1 | //ActivityThread.java |
所以对于AMS来说,
- AMS向Zygote发起启动应用的socket请求,Zygote收到请求fork出进程,返回进程的pid给AMS;
- 应用进程启动好后,执行入口main函数,通过attachApplication方法告诉AMS已经启动,同时传入应用进程的binder句柄IApplicationThread。
完成这两步,应用进程的启动过程才算完成。
下面看AMS的startProcessLocked启动应用进程时都做了些什么。
1 | //ActivityManagerService.java |
之所以要判断app.thread,是为了避免当应用进程正在启动的时候,假如又有另一个组件需要启动,导致重复拉起(创建)应用进程。
继续看重载方法startProcessLocked,
1 | //ActivityManagerService.java |
来到ZygoteProcess,
1 | //ZygoteProcess.java |
其中:
- openZygoteSocketIfNeeded打开本地socket
- zygoteSendArgsAndGetResult发送请求参数,其中带上了ActivityThread类名
- return返回的数据结构ProcessStartResult中会有pid字段
梳理一下:
注意:Zygote进程启动时已经创建好了虚拟机实例,所以由他fork出的应用进程可以直接继承过来用而无需创建。
下面来看Zygote是如何处理socket请求的。
Zygote处理socket请求
从 图解Android系统的启动 一文可知,在ZygoteInit的main函数中,会创建服务端socket,
1 | //ZygoteInit.java |
看到ZygoteServer,
1 | //ZygoteServer.java |
来到ZygoteConnection的runOnce,
1 | //ZygoteConnection.java |
handleChildProc方法调用了ZygoteInit的zygoteInit方法,里边主要做了3件事:
- 启动binder线程池(后面分析)
- 读取请求参数拿到ActivityThread类并执行他的main函数,执行thread.attach告知AMS并回传自己的binder句柄
- 执行Looper.loop()启动消息循环(代码前面有)
这样应用进程就启动起来了。梳理一下,
下面看下binder线程池是怎么启动的。
启动binder线程池
Zygote的跨进程通信没有使用binder,而是socket,所以应用进程的binder机制不是继承而来,而是进程创建后自己启动的。
前边可知,Zygote收到socket请求后会得到一个ZygoteConnection,他的runOnce会调用handleChildProc,
1 | //ZygoteConnection.java |
1 | //AndroidRuntime.cpp |
来到app_main.cpp,
1 | //app_main.cpp |
1 | //ProcessState.cpp |
ProcessState有两个宏定义值得注意一下,感兴趣可以看 一次Binder通信最大可以传输多大的数据 这篇文章,
1 | //ProcessState.cpp |
我们看下binder线程PoolThread长啥样,
1 | class PoolThread : public Thread |
1 | //IPCThreadState.cpp |
梳理一下binder的启动过程:
- 打开binder驱动
- 映射内存,分配缓冲区
- 运行binder线程,进入死循环,等待指令
总结
综上,Android应用进程的启动可以总结成以下步骤:
- 点击Launcher桌面的App图标
- AMS发起socket请求
- Zygote进程接收请求并处理参数
- Zygote进程fork出应用进程,应用进程继承得到虚拟机实例
- 应用进程启动binder线程池、运行ActivityThread类的main函数、启动Looper循环
完整流程图:
可见binder用得还是非常多的,下篇就补一补binder吧~
系列文章:
细节补充
- 抛异常清空堆栈帧:Zygote不是直接执行ActivityThread的main函数的,而是通过抛出一个异常进行捕获,捕获后再执行,这样可以清除初始化过程产生的调用堆栈,让ActivityThread的main函数看起来像个应用程序进程的入口函数。
参考资料
- 书籍 - Android进阶解密
- 博客 - 一次Binder通信最大可以传输多大的数据