关于与这个问题,其实在两年前研究Android启动过程的时候就发现,这是一个很有意思的地方,奈何工作繁忙的原因,一直没有时间去深究,最近终于有了闲暇的时间,就仔细的想了一下,总结出了一个原因,更准确的说是一种猜想,接下来就让咱们一起了分析一下:
通过Android源码我们可以发现:
1、启动一个Android应用程序在ZygoteInit.java的顺序如图:
2、当fork返回0时就表示进入了子进程的执行如图:
3、在handleChildProc函数处理的最后会调用 ZygoteInit.invokeStaticMain函数,在 ZygoteInit.invokeStaticMain函数中会抛出MethodAndArgsCaller异常,如图:
4、最终这个这个MethodAndArgsCaller异常会被ZygoteInit的main函数捕获,如图:
5、调用MethodAndArgsCaller的run方法,启动ActivityThread的main方法,开启Android的应用程序,如图:
以上就是Android每启动一个应用程序的主要过程,那我们来分析一下原因:
首先、我们要先清楚,抛异常这一操作会引发什么?
我们知道,当一个函数抛出异常后,这个异常会依次传递给调用它的函数,知道这个异常被捕获,如果这个异常一直没有被处理,最终就会引起程序的崩溃。
其次、在传递异常的时候,应用程序的栈发生了什么变化?
这就要牵涉到函数的执行模型了,我们知道,程序都是有一个个函数组成的(除了汇编程序),c/c++/java/..等高级语言编写的应用程序,在执行的时候,他们都拥有自己的栈空间(是一种先进后出的内存区域),用于存放函数的返回地址和函数的临时数据,每调用一个函数时,就会把函数的返回地址和相关数据压入栈中,当一个函数执行完后,就会从栈中弹出,cpu会根据函数的返回地址,执行上一个调用函数的下一条指令。
所以,在抛出异常后,如果异常没有在当前的函数中捕获,那么当前的函数执行就会异常的退出,从应用程序的栈弹出,并将这个异常传递给上一个函数,直到异常被捕获处理,否则,就会引起程序的崩溃。
那我们来看一下fork出新的子进程后,函数的执行流程:
handleChildProc——>ZygoteInit.invokeStaticMain(抛异常)——>ZygoteInit.main——>MethodAndArgsCaller.run——>ActivityThread.main
最后、为什么要用抛异常的方式去启动ActivityThread的main函数呢?而不直接在在ZygoteInit.invokeStaticMain中通过反射调用ActivityThread.main?
我们可以回想一下,无论我们写c程序还是java程序,他们都只有一个入口就是main函数,当main函数返回退出后就代表整个程序退出了,根据上面分析的函数的执行模型,程序的main函数应该是每一个应用程序最后退出的函数,应该位于栈的底部。同理,Android应用程序的入口是ActivityThread.main函数,所以它也应该位于新的进程的栈的ZygoteInit.main函数的上面,这样才能实现直接退出应用程序,但是Android每fork一个新进程的时候,它都会先调用handleChildProc函数做一些,子进程的处理,此时应用程序栈的最低部函数是handleChildProc,接着还会压入ZygoteInit.invokeStaticMain函数。
**所以这里通过抛异常的方式启动ActivityThread.main函数主要是清理应用程序栈中ZygoteInit.main以上的函数栈帧,以实现当ActivityThread.main函数退出时,能直接退出整个应用程序。
当ActivityThread的main退出后,就会退回到MethodAndArgsCaller.run而这个函数直接就退回到ZygoteInit.main函数,而ZygoteInit.main也无其他的操作,直接退出了函数,这样整个应用程序将会完全退出**。
好了,整个分析过程就完了,欢迎大家吐槽