美文网首页Android开发Android高级进阶Android进阶之路
搞懂Android应用启动过程,再也不怕面试官了

搞懂Android应用启动过程,再也不怕面试官了

作者: 小小小小怪兽_666 | 来源:发表于2020-11-25 21:55 被阅读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。

    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有两个宏定义值得注意一下:

    //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循环

    完整流程图:

    面试前的知识梳理,储备提升

    自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。

    关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

    • 架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO

    • Android高级UI与FrameWork源码:高级UI晋升+Framework内核解析+Android组件内核+数据持久化

    • 360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化

    • 解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件Jetpack

    • NDK模块开发:NDK基础知识体系+底层图片处理+音视频开发

    • 微信小程序:小程序介绍+UI开发+API操作+微信对接

    • Hybrid 开发与Flutter:Html5项目实战+Flutter进阶

    知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

    《507页Android开发相关源码解析》 《379页Android开发面试宝典》

    3.项目复盘

    实际上,面试的一二轮所问到的技术问题,很多都是围绕着你的项目展开,因此在面试前最后要做好的一件事情就是项目复盘。关于项目复盘,我个人的思路如下,可供参考:

    • 你在这个项目中承担了什么样的角色?
    • 这个项目的背景是什么,如果是技术项目,为什么要做?
    • 有哪些技术难点,是怎么解决的,是否还有更好的方案?
    • 你认为项目中是否有可以改进的点?
    • 这个项目解决了什么问题,最好用数据说话,这个数据又是怎么得出来的?

    提前把思路捋一捋,上面这些问题好好思考或准备一下,做到心中有谱以后,自然能够面试官聊得融洽,保持一个好的心态,通过的几率就会更大一些。

    资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图,以上资源均免费分享,以上内容均放在了开源项目:github 中已收录,大家可以自行获取(或者关注主页扫描加微信获取)。

    相关文章

      网友评论

        本文标题:搞懂Android应用启动过程,再也不怕面试官了

        本文链接:https://www.haomeiwen.com/subject/vjkoiktx.html