Android點擊Launcher應(yīng)用圖標(biāo)的應(yīng)用程序啟動過程(棧和進(jìn)程的創(chuàng)建)之Zygote是如何處理客戶端請求的

在上篇文章Android點擊Launcher應(yīng)用圖標(biāo)的應(yīng)用程序啟動過程(棧和進(jìn)程的創(chuàng)建)中我們分析了在Home中點擊應(yīng)用圖標(biāo)后的啟動過程及棧和進(jìn)程的創(chuàng)建過程雷则。我們講到了AMS通過socket通信到了Zygote,那么下面我們繼續(xù)看下Zygote是如何處理客戶端請求的。

處理客戶端請求

在ZygoteInit的main函數(shù)中通過zygoteServer.runSelectLoop(abiList)來接收客戶端請求的:

Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);

        while (true) {//無限循環(huán)
            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;
            }
         ...
                if (i == 0) {//index==0表示selcet接收到的是Zygote的socket的事件
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    try {
                        ZygoteConnection connection = peers.get(i);
//調(diào)用ZygoteConnection對象的processOneCommand方法聂使,ZygoteConnection是在index == 0時被添加到peers的
                        final Runnable command = connection.processOneCommand(this);
...
                            if (connection.isClosedByPeer()) {
                                connection.closeSocket();
                                peers.remove(i);
                                fds.remove(i);
                            }
                        }
                    } 
            }...
        }
    }

每當(dāng)有請求過來時,Zygote都會調(diào)用ZygoteConnection的processOneCommand()方法處理:

Runnable processOneCommand(ZygoteServer zygoteServer) {
        String args[];
        Arguments parsedArgs = null;
        FileDescriptor[] descriptors;

        try {
//讀取客戶端發(fā)送過來的參數(shù)
            args = readArgumentList();
            descriptors = mSocket.getAncillaryFileDescriptors();
        } catch (IOException ex) {
            throw new IllegalStateException("IOException on command socket", ex);
        }

   ...//省略一系列操作步驟
//fork一個新進(jìn)程
        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
                parsedArgs.instructionSet, parsedArgs.appDataDir);

        try {
            if (pid == 0) {//子進(jìn)程
                // in child
                zygoteServer.setForkChild();

                zygoteServer.closeServerSocket();
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;

                return handleChildProc(parsedArgs, descriptors, childPipeFd,
                        parsedArgs.startChildZygote);
            } else {//父進(jìn)程
                // In the parent. A pid < 0 indicates a failure and will be handled in
                // handleParentProc.
                IoUtils.closeQuietly(childPipeFd);
                childPipeFd = null;
                handleParentProc(pid, descriptors, serverPipeFd);
                return null;
            }
        } finally {
            IoUtils.closeQuietly(childPipeFd);
            IoUtils.closeQuietly(serverPipeFd);
        }
    }

Zygote在處理客戶端請求時會fork一個新的進(jìn)程,接下來首先看一下handleChildProc()方法:
/Volumes/android/WORKING_DIRECTORY/frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

 private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
            FileDescriptor pipeFd, boolean isZygote) {
        closeSocket();//關(guān)閉子進(jìn)程理郑,從Zygote fork過來的服務(wù)端socket
        if (descriptors != null) {
            try {
                Os.dup2(descriptors[0], STDIN_FILENO);
                Os.dup2(descriptors[1], STDOUT_FILENO);
                Os.dup2(descriptors[2], STDERR_FILENO);

                for (FileDescriptor fd: descriptors) {
                    IoUtils.closeQuietly(fd);
                }
            } catch (ErrnoException ex) {
                Log.e(TAG, "Error reopening stdio", ex);
            }
        }

        if (parsedArgs.niceName != null) {
            Process.setArgV0(parsedArgs.niceName);
        }

        // End of the postFork event.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        if (parsedArgs.invokeWith != null) {//沒有傳入--invoke-with肴甸,所以這里走的是else
            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    VMRuntime.getCurrentInstructionSet(),
                    pipeFd, parsedArgs.remainingArgs);

            // Should not get here.
            throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
        } else {
            if (!isZygote) { //如果是啟動一個Zygite的子Zygote線程寂殉,這里為false所以走else
                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
                        null /* classLoader */);
            } else {
                return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
                        parsedArgs.remainingArgs, null /* classLoader */);
            }
        }
    }

這里調(diào)用ZygoteInit的zygoteInit()方法:

public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
      if (RuntimeInit.DEBUG) {
          Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
      }

      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
//指定系統(tǒng)log輸出到andorid
     RuntimeInit.redirectLogStreams();
      RuntimeInit.commonInit();//初始化系統(tǒng)屬性
      ZygoteInit.nativeZygoteInit();
////應(yīng)用初始化
      return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
  }

這里注釋已經(jīng)說的很清楚了我們來看一下這幾個方法的實現(xiàn)

  1. RuntimeInit.redirectLogStreams():
public static void redirectLogStreams() {
        System.out.close();
        System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
        System.err.close();
        System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
    }

這里將System.out 和 System.err 輸出重定向到Android 的Log系統(tǒng)。

  1. RuntimeInit.commonInit():
protected static final void commonInit() {
        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
        RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone"));
        LogManager.getLogManager().reset();
        new AndroidConfig();
        String userAgent = getDefaultUserAgent();
        System.setProperty("http.agent", userAgent);

        NetworkManagementSocketTagger.install();
        String trace = SystemProperties.get("ro.kernel.android.tracing");
        if (trace.equals("1")) {
            Slog.i(TAG, "NOTE: emulator trace profiling enabled");
            Debug.enableEmulatorTraceOutput();
        }

        initialized = true;
    }

初始化了一些系統(tǒng)屬性原在,其中最重要的一點就是設(shè)置了一個未捕捉異常的handler友扰,當(dāng)代碼有任何未知異常,就會執(zhí)行它庶柿, 調(diào)試過Android代碼的同學(xué)經(jīng)炒骞郑看到的"*** FATAL EXCEPTION IN SYSTEM PROCESS" 打印就出自這里的LoggingHandler對象。

  1. ZygoteInit.nativeZygoteInit():
    該方法是一個本地方法浮庐, 最終會調(diào)用app_main的onZygoteInit函數(shù) 這里的作用是在新進(jìn)程中引入Binder甚负,也就說通過nativeZygoteInit以后,新的進(jìn)程就可以使用Binder進(jìn)程通信了审残。
  2. RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader):
 protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) {
        // If the application calls System.exit(), terminate the process
        // immediately without running any shutdown hooks.  It is not possible to
        // shutdown an Android application gracefully.  Among other things, the
        // Android runtime shutdown hooks close the Binder driver, which can cause
        // leftover running threads to crash before the process actually exits.
        nativeSetExitWithoutCleanup(true);

        // We want to be fairly aggressive about heap utilization, to avoid
        // holding on to a lot of memory that isn't needed.
        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

        final Arguments args = new Arguments(argv);

        // The end of of the RuntimeInit event (see #zygoteInit).
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        // Remaining arguments are passed to the start class's static main
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }

在applicationInit()的最后腊敲,會通過調(diào)用findStaticMain來調(diào)用args.startClass這個類的main()方法。在前面介紹socket的客戶端代碼時维苔,在startProcessLocked()中傳入的這個類為"android.app.ActivityThread"碰辅。所以接下來findStaticMain()的功能相信大家都已經(jīng)知道了,就是調(diào)用ActivityThread類的main()介时,下面是代碼:

protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;

        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }

        /*
         * 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.
         */
        return new MethodAndArgsCaller(m, argv);
    }

這個方法本身功能就是調(diào)用ActivityThread類的main()没宾,沒什么可說的。這里痛class獲取ActivityThreadActivityThread類的main()方法封裝在new MethodAndArgsCaller中并返回到ZygoteInit.main()中(老的版本統(tǒng)統(tǒng)一個奇怪的方式:拋出ZygoteInit.MethodAndArgsCaller異常會在ZygoteInit.main()中被捕獲處理):

public static void main(String argv[]) {
...
        // We're in the child process and have exited the select loop. Proceed to execute the
        // command.
        if (caller != null) {
            caller.run();
        }
    }

這里的caller是com.android.internal.os.RuntimeInit.MethodAndArgsCaller沸柔,它實現(xiàn)了Runnable接口循衰,這里調(diào)用器run方法。這里需要注意的是:cller.run在子進(jìn)程(即新的進(jìn)程褐澎,不是Zygote進(jìn)程)中的動作会钝。還記得前面介紹的processOneCommand()方法嗎?我們在processOneCommand中創(chuàng)建了一個新的進(jìn)程工三。如果讀者還有不明白這里為什么是在子進(jìn)程迁酸,可以自行學(xué)習(xí)Linux fork()的原理。那么我們繼續(xù)看下它的run方法里做了什么:

public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }

這里 看到了mMethod.invoke(null, new Object[] { mArgs })俭正,原來最后還是會調(diào)用invoke方法通過反射的方式調(diào)用ActivityThread的main方法奸鬓。
到了這里,相信大家都會有一個疑問:既然最后還是通過invoke來反射調(diào)用main方法掸读,那繞這一大圈子到底在折騰什么串远?
有疑問的讀者宏多,有沒有去思考過另外一個問題:我們?yōu)槭裁匆ㄟ^Zygote去創(chuàng)建進(jìn)程,而不是直接創(chuàng)建一個新的進(jìn)程出來呢澡罚?這就要從Zygote創(chuàng)建進(jìn)程的機(jī)制來解釋伸但。相信我們還記得在ZygoteInit的main函數(shù)中我們通過preload來預(yù)加載類和資源。所以這些被預(yù)加載的類和資源都存在于Zygote進(jìn)程中留搔。在通過Zygote創(chuàng)建進(jìn)程時更胖,我們是通過fork來創(chuàng)建的。 一個進(jìn)程調(diào)用fork()函數(shù)后催式,系統(tǒng)先給新的進(jìn)程分配資源,例如存儲數(shù)據(jù)和代碼的空間避归。然后把原來的進(jìn)程的所有值都復(fù)制到新的新進(jìn)程中荣月,只有少數(shù)值與原來的進(jìn)程的值不同,相當(dāng)于克隆了一個自己梳毙。所以哺窄,Zygote通過fork的方式創(chuàng)建新的應(yīng)用進(jìn)程的同時,會將對系統(tǒng)的(主要是framework中的)一些類和資源的引用同時復(fù)制給子進(jìn)程账锹,這樣子進(jìn)程中就可以使用這些資源了萌业。這也是為什么所有的應(yīng)用程序可以共享Framework中的類和資源的原因。
好了到這里就已經(jīng)創(chuàng)建好新的應(yīng)用進(jìn)程了奸柬,接下來就是應(yīng)用的初始化過程了生年,下篇文章再見了!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廓奕,一起剝皮案震驚了整個濱河市抱婉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桌粉,老刑警劉巖蒸绩,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铃肯,居然都是意外死亡患亿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門押逼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來步藕,“玉大人,你說我怎么就攤上這事挑格∈ィ” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵恕齐,是天一觀的道長乞娄。 經(jīng)常有香客問我瞬逊,道長,這世上最難降的妖魔是什么仪或? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任确镊,我火速辦了婚禮,結(jié)果婚禮上范删,老公的妹妹穿的比我還像新娘蕾域。我一直安慰自己,他們只是感情好到旦,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布旨巷。 她就那樣靜靜地躺著,像睡著了一般添忘。 火紅的嫁衣襯著肌膚如雪采呐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天搁骑,我揣著相機(jī)與錄音斧吐,去河邊找鬼。 笑死仲器,一個胖子當(dāng)著我的面吹牛煤率,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乏冀,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蝶糯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辆沦?” 一聲冷哼從身側(cè)響起裳涛,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎众辨,沒想到半個月后端三,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鹃彻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年郊闯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛛株。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡团赁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谨履,到底是詐尸還是另有隱情欢摄,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布笋粟,位于F島的核電站怀挠,受9級特大地震影響析蝴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绿淋,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一闷畸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吞滞,春花似錦佑菩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佩捞,卻和暖如春绞幌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背失尖。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工啊奄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留渐苏,地道東北人掀潮。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像琼富,于是被迫代替她去往敵國和親仪吧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容