為什麼Android每當啟動一個新的應用程式時會通過“throw new ZygoteInit.MethodAndArgsCaller(m, argv)”進入ActivityThread的main函式
關於與這個問題,其實在兩年前研究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也無其他的操作,直接退出了函式,這樣整個應用程式將會完全退出**。
好了,整個分析過程就完了,歡迎大家吐槽