詳解Android應(yīng)用main函數(shù)的調(diào)用
啟動(dòng)App進(jìn)程
Activity啟動(dòng)過(guò)程的一環(huán)是調(diào)用ActivityStackSupervisor.startSpecificActivityLocked,如果App所在進(jìn)程還不存在,首先調(diào)用AMS的startProcessLocked:
void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid, true); r.task.stack.setLaunchTime(r); if (app != null && app.thread != null) { //... } mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, "activity", r.intent.getComponent(), false, false, true); }
startProcessLocked函數(shù)有多個(gè)重載,看最長(zhǎng)的那個(gè),最重要是調(diào)用了Process.start。
if (entryPoint == null) entryPoint = "android.app.ActivityThread"; Process.ProcessStartResult startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, app.info.dataDir, entryPointArgs);
第一個(gè)參數(shù)是android.app.ActivityThread,執(zhí)行的目標(biāo),后文用到再說(shuō)。
Process.start簡(jiǎn)單地調(diào)用了startViaZygote,封裝一些參數(shù),再調(diào)用zygoteSendArgsAndGetResult。顧名思義,接下來(lái)進(jìn)程的啟動(dòng)工作交給Zygote。
Zygote
Zygote翻譯過(guò)來(lái)意思是“受精卵”,這也是Zygote的主要工作——孵化進(jìn)程。概括Zygote的主要工作有以下三點(diǎn),ZygoteInit的main函數(shù)也清晰地體現(xiàn)了。Zygote的啟動(dòng)和其他作用另文分析,這次關(guān)注Zygote對(duì)Socket的監(jiān)聽(tīng)。
1.registerZygoteSocket:?jiǎn)?dòng)Socket的Server端
2.preload:預(yù)加載資源
3.startSystemServer:?jiǎn)?dòng)system_server進(jìn)程
public static void main(String argv[]) { // Mark zygote start. This ensures that thread creation will throw // an error. ZygoteHooks.startZygoteNoThreadCreation(); try { //... registerZygoteSocket(socketName); //... preload(); //... if (startSystemServer) { startSystemServer(abiList, socketName); } //... runSelectLoop(abiList); //... } catch (MethodAndArgsCaller caller) { caller.run(); } catch (Throwable ex) { //... } }
最后Zygote執(zhí)行runSelectLoop,無(wú)限循環(huán)等待處理進(jìn)程啟動(dòng)的請(qǐng)求。
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); fds.add(sServerSocket.getFileDescriptor()); peers.add(null); while (true) { StructPollfd[] pollFds = new StructPollfd[fds.size()]; for (int i = 0; i < pollFds.length; ++i) { pollFds[i] = new StructPollfd(); pollFds[i].fd = fds.get(i); pollFds[i].events = (short) POLLIN; } try { Os.poll(pollFds, -1); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } for (int i = pollFds.length - 1; i >= 0; --i) { if ((pollFds[i].revents & POLLIN) == 0) { continue; } if (i == 0) { ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { boolean done = peers.get(i).runOnce(); if (done) { peers.remove(i); fds.remove(i); } } } } }
與Zygote通信使用的IPC方式是socket,類(lèi)型是LocalSocket,實(shí)質(zhì)是對(duì)Linux的LocalSocket的封裝。與記憶中socket綁定需要IP和端口不同,LocalSocket使用FileDescriptor文件描述符,它可以表示文件,也可以表示socket。
runSelectLoop維護(hù)了兩個(gè)列表,分別保存文件描述符FileDescriptor和ZygoteConnection,兩者一一對(duì)應(yīng)。index=0的FileDescriptor表示ServerSocket,因此index=0的ZygoteConnection用null填充。
在每次循環(huán)中,判斷fds里哪個(gè)可讀:
- 當(dāng)i=0時(shí),表示有新的client,調(diào)用acceptCommandPeer創(chuàng)建ZygoteConnection并保存
- 當(dāng)i>0時(shí),表示已建立連接的socket中有新的命令,調(diào)用runOnce函數(shù)執(zhí)行
fork進(jìn)程
runOnce函數(shù)非常長(zhǎng),我們只關(guān)注進(jìn)程創(chuàng)建部分。
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller { //... try { //... pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet, parsedArgs.appDataDir); } catch (ErrnoException ex) { //... } try { if (pid == 0) { // in child IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); // should never get here, the child is expected to either // throw ZygoteInit.MethodAndArgsCaller or exec(). return true; } else { // in parent...pid of < 0 means failure IoUtils.closeQuietly(childPipeFd); childPipeFd = null; return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); } } finally { //... } }
首先調(diào)用了forkAndSpecialize函數(shù),創(chuàng)建進(jìn)程返回一個(gè)pid。
public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true); // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); } VM_HOOKS.postForkCommon(); return pid; }
在forkAndSpecialize中核心就是利用JNI調(diào)用native的fork函數(shù),調(diào)用之前會(huì)執(zhí)行VM_HOOKS.preFork(),調(diào)用之后執(zhí)行VM_HOOKS.postForkCommon()。
VM_HOOKS.preFork()的功能是停止Zygote的4個(gè)Daemon子線(xiàn)程的運(yùn)行,確保Zygote是單線(xiàn)程,提升fork效率。當(dāng)線(xiàn)程停止之后初始化gc堆。VM_HOOKS.postForkCommon()可以看作是逆操作,關(guān)于虛擬機(jī)更加深入的內(nèi)容暫不討論。
native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
nativeForkSystemServer是一個(gè)native函數(shù),對(duì)應(yīng)com_android_internal_os_Zygote.cpp的com_android_internal_os_Zygote_nativeForkAndSpecialize,繼續(xù)調(diào)用了ForkAndSpecializeCommon,最核心一句則是調(diào)用fork函數(shù)。
pid_t pid = fork();
簡(jiǎn)單回憶fork函數(shù)作用,它復(fù)制當(dāng)前進(jìn)程,屬性和當(dāng)前進(jìn)程相同,使用copy on write(寫(xiě)時(shí)復(fù)制)。執(zhí)行函數(shù)后,新進(jìn)程已經(jīng)創(chuàng)建,返回的pid=0;對(duì)于被復(fù)制的進(jìn)程,返回新進(jìn)程的pid;出現(xiàn)錯(cuò)誤時(shí),返回-1。
因此,執(zhí)行forkAndSpecialize函數(shù)后,runOnce后續(xù)的代碼分別在兩個(gè)進(jìn)程中執(zhí)行,判斷當(dāng)前的pid,區(qū)分是在當(dāng)前進(jìn)程還是新進(jìn)程。
- pid == 0:新進(jìn)程,調(diào)用handleChildProc
- pid != 0:當(dāng)前進(jìn)程,調(diào)用handleParentProc
handleParentProc函數(shù)是當(dāng)前進(jìn)程進(jìn)行清理的過(guò)程,比較簡(jiǎn)單。我們重點(diǎn)來(lái)看新進(jìn)程開(kāi)展工作的handleChildProc函數(shù)。
新進(jìn)程的初始化
private void handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr) throws ZygoteInit.MethodAndArgsCaller { //... if (parsedArgs.invokeWith != null) { WrapperInit.execApplication(parsedArgs.invokeWith, parsedArgs.niceName, parsedArgs.targetSdkVersion, VMRuntime.getCurrentInstructionSet(), pipeFd, parsedArgs.remainingArgs); } else { RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, null /* classLoader */); } }
兩種啟動(dòng)方式:
- WrapperInit.execApplication
- RuntimeInit.zygoteInit
第一種的目的不太懂,先掛起,后續(xù)分析。
第二種RuntimeInit.zygoteInit,繼續(xù)調(diào)用applicationInit,離我們的目標(biāo)越來(lái)越近了,最終調(diào)用到invokeStaticMain。
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { Class<?> cl; try { cl = Class.forName(className, true, classLoader); } catch (ClassNotFoundException ex) { //... } Method m; try { m = cl.getMethod("main", new Class[] { String[].class }); } catch (NoSuchMethodException ex) { //... } int modifiers = m.getModifiers(); if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) { throw new RuntimeException( "Main method is not public and static on " + className); } throw new ZygoteInit.MethodAndArgsCaller(m, argv); }
在這里使用反射獲得需要被執(zhí)行的類(lèi)和函數(shù),目標(biāo)函數(shù)當(dāng)然就是main,目標(biāo)類(lèi)來(lái)自ActivityManagerService.startProcessLocked里的變量entryPoint,前面有說(shuō)過(guò)。
entryPoint = "android.app.ActivityThread"
最后一句執(zhí)行throw new ZygoteInit.MethodAndArgsCaller(m, argv),讓人有些費(fèi)解。
public static class MethodAndArgsCaller extends Exception implements Runnable { //... public void run() { try { mMethod.invoke(null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { //... } } }
通過(guò)上面的分析,容易知道MethodAndArgsCaller就是App的主線(xiàn)程,里面的run方法實(shí)現(xiàn)了反射的調(diào)用。什么時(shí)候觸發(fā)執(zhí)行,為什么要這樣設(shè)計(jì)?
既然MethodAndArgsCaller是異常,拋出它肯定某個(gè)地方會(huì)接收,回顧一路的調(diào)用鏈:
- ZytoteInit.main
- ZytoteInit.runSelectLoop
- ZygoteConnection.runOnce
- ZygoteConnection.handleChildProc
- RuntimeInit.zygoteInit
- RuntimeInit.applicationInit
- RuntimeInit.invokeStaticMain
看前面的ZytoteInit.main函數(shù),catch了MethodAndArgsCaller異常,直接調(diào)用了run函數(shù)。注釋里解釋了為什么要這樣做:
This throw gets caught in ZygoteInit.main(), which responds by invoking the exception's run() method. This arrangement clears up all the stack frames that were required in setting up the process.
函數(shù)在虛擬機(jī)是保存在棧中,每調(diào)用一個(gè)函數(shù),就將函數(shù)相關(guān)數(shù)據(jù)壓入棧;執(zhí)行完函數(shù),將函數(shù)從棧中彈出。因此,棧底的就是main函數(shù)。
在上面的研究中,新進(jìn)程創(chuàng)建后,經(jīng)歷一系列函數(shù)的調(diào)用才到main函數(shù),如果直接調(diào)用main函數(shù),調(diào)用鏈中關(guān)于初始化的函數(shù)會(huì)一直存在。為了清理這部分函數(shù),使用了拋出異常的方式,沒(méi)有捕獲異常的函數(shù)會(huì)馬上結(jié)束,ZytoteInit.main之上的函數(shù)都會(huì)結(jié)束,達(dá)到清理的目的。
最后補(bǔ)充一點(diǎn),從handleChildProc函數(shù)開(kāi)始,一系列過(guò)程調(diào)用了ActivityThread的main函數(shù),這不是啟動(dòng)App獨(dú)有的,后續(xù)研究啟動(dòng)SystemServer進(jìn)程時(shí),你會(huì)發(fā)現(xiàn)邏輯都是一樣。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android app在后臺(tái)運(yùn)行彈出彈窗
這篇文章主要為大家介紹了android app在后臺(tái)運(yùn)行彈出彈窗,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11Android訪(fǎng)問(wèn)assets本地json文件的方法
這篇文章主要介紹了Android訪(fǎng)問(wèn)assets本地json文件的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android Parcleable接口的調(diào)用源碼層分析
這篇文章主要給大家介紹了關(guān)于利用Kotlin如何實(shí)現(xiàn)Android開(kāi)發(fā)中的Parcelable的相關(guān)資料,并且給大家介紹了關(guān)于Android Parcleable源碼層問(wèn)題,需要的朋友可以參考下2022-12-12Intellij IDEA + Android SDK + Genymotion Emulator打造最佳Android
本文介紹Lorinnn在開(kāi)發(fā)Android過(guò)程不斷跌打滾爬中安裝的一套開(kāi)發(fā)環(huán)境,相信你在使用后同樣有不錯(cuò)的體會(huì)。2014-07-07Android RecyclerView自定義上拉和下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView自定義上拉和下拉刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android獲取屏幕方向及鍵盤(pán)狀態(tài)的小例子
很多開(kāi)發(fā)Android的網(wǎng)友可能需要判斷當(dāng)前的屏幕方向或鍵盤(pán)狀態(tài),下面的代碼可以判斷出橫屏landscape和常規(guī)的portrait縱握方式,如果使用的是G1這樣有QWERTY鍵盤(pán)硬件的,還可以判斷屏幕方向以及鍵盤(pán)的拉出狀態(tài)。2013-05-05Android 應(yīng)用適配 Android 7.0 權(quán)限要求詳解
今天小編就為大家分享一篇Android 應(yīng)用適配 Android 7.0 權(quán)限要求詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Android apk無(wú)法安裝及閃退問(wèn)題解決辦法
這篇文章主要介紹了Android apk無(wú)法安裝及閃退問(wèn)題的相關(guān)資料,這里對(duì)閃退問(wèn)題進(jìn)行詳解及解決步驟的詳細(xì)介紹,需要的朋友可以參考下2017-07-07Android?Banner本地和網(wǎng)絡(luò)輪播圖使用介紹
大家好,本篇文章講的是Android?Banner本地和網(wǎng)絡(luò)輪播圖使用介紹,感興趣的同學(xué)趕快來(lái)看一看吧,希望本篇文章對(duì)你起到幫助2021-11-11Android View教程之自定義驗(yàn)證碼輸入框效果
這篇文章主要給大家介紹了關(guān)于Android View教程之自定義驗(yàn)證碼輸入框效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05