图解 | 一图摸清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图标来启动应用进程了。

image

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

image

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句柄,来实现双向通信

image

那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字段

梳理一下:

image

注意: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()启动消息循环(代码前面有)

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

image

下面看下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循环

完整流程图:

image

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

系列文章:

细节补充

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

参考资料


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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343