誰調(diào)用了Android應(yīng)用的main函數(shù)

源碼Android 7.1.1

Android常識骡和,App主線程初始化了Looper估脆,調(diào)用prepare的地方是ActivityThread.main函數(shù)件相。問題來了韵卤,App的main函數(shù)在哪兒調(diào)用,本文嘗試尋找答案着撩。

啟動App進程

Activity啟動過程的一環(huán)是調(diào)用ActivityStackSupervisor.startSpecificActivityLocked诅福,如果App所在進程還不存在,首先調(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ù)有多個重載睹酌,看最長的那個权谁,最重要是調(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);

第一個參數(shù)是android.app.ActivityThread憋沿,執(zhí)行的目標,后文用到再說沪猴。

Process.start簡單地調(diào)用了startViaZygote辐啄,封裝一些參數(shù)采章,再調(diào)用zygoteSendArgsAndGetResult。顧名思義壶辜,接下來進程的啟動工作交給Zygote悯舟。

Zygote

Zygote翻譯過來意思是“受精卵”,這也是Zygote的主要工作——孵化進程砸民。概括Zygote的主要工作有以下三點抵怎,ZygoteInit的main函數(shù)也清晰地體現(xiàn)了。Zygote的啟動和其他作用另文分析岭参,這次關(guān)注Zygote對Socket的監(jiān)聽反惕。

  1. registerZygoteSocket:啟動Socket的Server端
  2. preload:預(yù)加載資源
  3. startSystemServer:啟動system_server進程
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,無限循環(huán)等待處理進程啟動的請求演侯。

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姿染,類型是LocalSocket,實質(zhì)是對Linux的LocalSocket的封裝秒际。與記憶中socket綁定需要IP和端口不同悬赏,LocalSocket使用FileDescriptor文件描述符,它可以表示文件娄徊,也可以表示socket闽颇。

runSelectLoop維護了兩個列表,分別保存文件描述符FileDescriptor和ZygoteConnection寄锐,兩者一一對應(yīng)进萄。index=0的FileDescriptor表示ServerSocket,因此index=0的ZygoteConnection用null填充锐峭。

在每次循環(huán)中中鼠,判斷fds里哪個可讀:

  • 當i=0時,表示有新的client沿癞,調(diào)用acceptCommandPeer創(chuàng)建ZygoteConnection并保存
  • 當i>0時援雇,表示已建立連接的socket中有新的命令,調(diào)用runOnce函數(shù)執(zhí)行

fork進程

runOnce函數(shù)非常長椎扬,我們只關(guā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)建進程返回一個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)用之前會執(zhí)行VM_HOOKS.preFork(),調(diào)用之后執(zhí)行VM_HOOKS.postForkCommon()揖铜。

VM_HOOKS.preFork()的功能是停止Zygote的4個Daemon子線程的運行茴丰,確保Zygote是單線程,提升fork效率。當線程停止之后初始化gc堆贿肩。VM_HOOKS.postForkCommon()可以看作是逆操作峦椰,關(guān)于虛擬機更加深入的內(nèi)容暫不討論。

 native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);

nativeForkSystemServer是一個native函數(shù)汰规,對應(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();

簡單回憶fork函數(shù)作用滔金,它復(fù)制當前進程,屬性和當前進程相同茂嗓,使用copy on write(寫時復(fù)制)鹦蠕。執(zhí)行函數(shù)后,新進程已經(jīng)創(chuàng)建在抛,返回的pid=0钟病;對于被復(fù)制的進程,返回新進程的pid刚梭;出現(xiàn)錯誤時肠阱,返回-1。如下圖:

image.png

因此朴读,執(zhí)行forkAndSpecialize函數(shù)后屹徘,runOnce后續(xù)的代碼分別在兩個進程中執(zhí)行,判斷當前的pid衅金,區(qū)分是在當前進程還是新進程噪伊。

  • pid == 0:新進程,調(diào)用handleChildProc
  • pid != 0:當前進程氮唯,調(diào)用handleParentProc

handleParentProc函數(shù)是當前進程進行清理的過程鉴吹,比較簡單。我們重點來看新進程開展工作的handleChildProc函數(shù)惩琉。

新進程的初始化

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 */);
    }
}

兩種啟動方式:

  • WrapperInit.execApplication
  • RuntimeInit.zygoteInit

第一種的目的不太懂豆励,先掛起,后續(xù)分析瞒渠。

第二種RuntimeInit.zygoteInit良蒸,繼續(xù)調(diào)用applicationInit,離我們的目標越來越近了伍玖,最終調(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í)行的類和函數(shù),目標函數(shù)當然就是main窍箍,目標類來自ActivityManagerService.startProcessLocked里的變量entryPoint串纺,前面有說過丽旅。

entryPoint = "android.app.ActivityThread"

最后一句執(zhí)行throw new ZygoteInit.MethodAndArgsCaller(m, argv),讓人有些費解造垛。

public static class MethodAndArgsCaller extends Exception
        implements Runnable {
   //...

    public void run() {
        try {
            mMethod.invoke(null, new Object[] { mArgs });
        } catch (IllegalAccessException ex) {
            //...
        }
    }
}

通過上面的分析,容易知道MethodAndArgsCaller就是App的主線程晰搀,里面的run方法實現(xiàn)了反射的調(diào)用五辽。什么時候觸發(fā)執(zhí)行,為什么要這樣設(shè)計外恕?

既然MethodAndArgsCaller是異常杆逗,拋出它肯定某個地方會接收,回顧一路的調(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ù)在虛擬機是保存在棧中尚洽,每調(diào)用一個函數(shù)悔橄,就將函數(shù)相關(guān)數(shù)據(jù)壓入棧;執(zhí)行完函數(shù)腺毫,將函數(shù)從棧中彈出癣疟。因此,棧底的就是main函數(shù)潮酒。

在上面的研究中睛挚,新進程創(chuàng)建后,經(jīng)歷一系列函數(shù)的調(diào)用才到main函數(shù)急黎,如果直接調(diào)用main函數(shù)扎狱,調(diào)用鏈中關(guān)于初始化的函數(shù)會一直存在。為了清理這部分函數(shù)勃教,使用了拋出異常的方式淤击,沒有捕獲異常的函數(shù)會馬上結(jié)束,ZytoteInit.main之上的函數(shù)都會結(jié)束故源,達到清理的目的遭贸。

最后補充一點,從handleChildProc函數(shù)開始心软,一系列過程調(diào)用了ActivityThread的main函數(shù)壕吹,這不是啟動App獨有的,后續(xù)研究啟動SystemServer進程時删铃,你會發(fā)現(xiàn)邏輯都是一樣耳贬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猎唁,隨后出現(xiàn)的幾起案子咒劲,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腐魂,死亡現(xiàn)場離奇詭異帐偎,居然都是意外死亡,警方通過查閱死者的電腦和手機蛔屹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門削樊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兔毒,你說我怎么就攤上這事漫贞。” “怎么了育叁?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵迅脐,是天一觀的道長。 經(jīng)常有香客問我豪嗽,道長谴蔑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任龟梦,我火速辦了婚禮树碱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘变秦。我一直安慰自己成榜,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布蹦玫。 她就那樣靜靜地躺著赎婚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪樱溉。 梳的紋絲不亂的頭發(fā)上挣输,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音福贞,去河邊找鬼撩嚼。 笑死,一個胖子當著我的面吹牛挖帘,可吹牛的內(nèi)容都是我干的完丽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼拇舀,長吁一口氣:“原來是場噩夢啊……” “哼逻族!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骄崩,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤聘鳞,失蹤者是張志新(化名)和其女友劉穎薄辅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抠璃,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡站楚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了搏嗡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窿春。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖彻况,靈堂內(nèi)的尸體忽然破棺而出谁尸,到底是詐尸還是另有隱情舅踪,我是刑警寧澤纽甘,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站抽碌,受9級特大地震影響悍赢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜货徙,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一左权、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痴颊,春花似錦赏迟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽时甚。三九已至,卻和暖如春糕再,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玉转。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工突想, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人究抓。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓猾担,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刺下。 傳聞我的和親對象是個殘疾皇子垒探,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353