Android USAP 進(jìn)程啟動(dòng)流程

從Android Q(10)開始剃法,Google引入了一種新的機(jī)制俄占,加快了app的啟動(dòng)時(shí)間舔痕,具體請(qǐng)看Android Framework | 一種新型的應(yīng)用啟動(dòng)機(jī)制:USAP食听,本篇將會(huì)詳細(xì)介紹USAP 進(jìn)程啟動(dòng)的流程奸披。

Activity啟動(dòng)流程 上篇(Android 10),我們得知在Activity啟動(dòng)過程中蝎土,我們會(huì)調(diào)用到\frameworks\base\core\java\android\os\ZygoteProcess.javastart方法视哑,然后調(diào)用startViaZygote(),其實(shí)在調(diào)用startViaZygote()之前還有一步:

    public final Process.ProcessStartResult start(@NonNull final String processClass,
                                                  final String niceName,
                                                  int uid, int gid, @Nullable int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  @Nullable String seInfo,
                                                  @NonNull String abi,
                                                  @Nullable String instructionSet,
                                                  @Nullable String appDataDir,
                                                  @Nullable String invokeWith,
                                                  @Nullable String packageName,
                                                  boolean useUsapPool,
                                                  @Nullable String[] zygoteArgs) {
        // TODO (chriswailes): Is there a better place to check this value?
        //---------------------------- 就是這里 ----------------------------
        if (fetchUsapPoolEnabledPropWithMinInterval()) {
            informZygotesOfUsapPoolStatus();
        }

        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                    packageName, useUsapPool, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }

從函數(shù)名稱誊涯,我們可以得知挡毅,這里用fetchUsapPoolEnabledPropWithMinInterval判斷系統(tǒng)是否開啟了USAP功能,如果開啟則調(diào)用informZygotesOfUsapPoolStatus():

    /**
     * Sends messages to the zygotes telling them to change the status of their USAP pools.  If
     * this notification fails the ZygoteProcess will fall back to the previous behavior.
     */
    private void informZygotesOfUsapPoolStatus() {
        final String command = "1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n";

        synchronized (mLock) {
            try {
                attemptConnectionToPrimaryZygote();

                primaryZygoteState.mZygoteOutputWriter.write(command);
                primaryZygoteState.mZygoteOutputWriter.flush();
            } catch (IOException ioe) {
                mUsapPoolEnabled = !mUsapPoolEnabled;
                Log.w(LOG_TAG, "Failed to inform zygotes of USAP pool status: "
                        + ioe.getMessage());
                return;
            }

            if (mZygoteSecondarySocketAddress != null) {
                try {
                    attemptConnectionToSecondaryZygote();

                    try {
                        secondaryZygoteState.mZygoteOutputWriter.write(command);
                        secondaryZygoteState.mZygoteOutputWriter.flush();

                        // Wait for the secondary Zygote to finish its work.
                        secondaryZygoteState.mZygoteInputStream.readInt();
                    } catch (IOException ioe) {
                        throw new IllegalStateException(
                                "USAP pool state change cause an irrecoverable error",
                                ioe);
                    }
                } catch (IOException ioe) {
                    // No secondary zygote present.  This is expected on some devices.
                }
            }

            // Wait for the response from the primary zygote here so the primary/secondary zygotes
            // can work concurrently.
            try {
                // Wait for the primary zygote to finish its work.
                primaryZygoteState.mZygoteInputStream.readInt();
            } catch (IOException ioe) {
                throw new IllegalStateException(
                        "USAP pool state change cause an irrecoverable error",
                        ioe);
            }
        }
    }

可以看到該函數(shù)將調(diào)用attemptConnectionToPrimaryZygoteattemptConnectionToSecondaryZygote()這其實(shí)和Zygote()運(yùn)行的位數(shù)有關(guān)暴构,32或者64位慷嗜,他們都調(diào)用了ZygoteState.connect,只是傳入的參數(shù)不同丹壕,我們看attemptConnectionToPrimaryZygote:

    private void attemptConnectionToPrimaryZygote() throws IOException {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            primaryZygoteState =
                    ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);

            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
            maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
            maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);
        }
    }

然后看ZygoteState.connect:

        static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress,
                @Nullable LocalSocketAddress usapSocketAddress)
                throws IOException {

            DataInputStream zygoteInputStream;
            BufferedWriter zygoteOutputWriter;
            final LocalSocket zygoteSessionSocket = new LocalSocket();

            if (zygoteSocketAddress == null) {
                throw new IllegalArgumentException("zygoteSocketAddress can't be null");
            }

            try {
                zygoteSessionSocket.connect(zygoteSocketAddress);
                zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
                zygoteOutputWriter =
                        new BufferedWriter(
                                new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
                                Zygote.SOCKET_BUFFER_SIZE);
            } catch (IOException ex) {
                try {
                    zygoteSessionSocket.close();
                } catch (IOException ignore) { }

                throw ex;
            }

            return new ZygoteState(zygoteSocketAddress, usapSocketAddress,
                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
        }

這段代碼比較好理解庆械,就是創(chuàng)建一個(gè)LocalSocketZygote建立連接,并獲取輸入輸出流設(shè)置到ZygoteState中菌赖,待會(huì)兒我們會(huì)用到缭乘,至此attemptConnectionToPrimaryZygote調(diào)用完成,回到上面的informZygotesOfUsapPoolStatus琉用,代碼將command寫給了Zygote堕绩,而command"1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n",這里mUsapPoolEnabled自然為true邑时, 記住這個(gè)command奴紧,接下來可以到zygote進(jìn)程中查看了。

Android系統(tǒng)啟動(dòng)流程末尾晶丘,我們說到Zygote進(jìn)程會(huì)執(zhí)行zygoteServer.runSelectLoop(abiList)黍氮,接收并處理AMS傳過來的消息唐含,比如fork app進(jìn)程。這里我們直接看runSelectLoop函數(shù)即可:

Runnable runSelectLoop(String abiList) {
       while (true) {
            ....
            try {
                Os.poll(pollFDs, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            ....
                ....
                        ZygoteConnection connection = peers.get(pollIndex);
                        final Runnable command = connection.processOneCommand(this);

       }
}

這里runSelectLoop會(huì)使用epoll機(jī)制沫浆,阻塞在Os.poll(pollFDs, -1)捷枯,獲取對(duì)方連接請(qǐng)求后,執(zhí)行\frameworks\base\core\java\com\android\internal\os\ZygoteConnection.javaprocessOneCommand方法:

    Runnable processOneCommand(ZygoteServer zygoteServer) {
        ....
        parsedArgs = new ZygoteArguments(args);
        ....
        if (parsedArgs.mUsapPoolStatusSpecified) {
            return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
        }
        ....

ZygoteArguments有代碼如下:

    ....
        ....
            } else if (arg.startsWith("--usap-pool-enabled=")) {
                mUsapPoolStatusSpecified = true;
                mUsapPoolEnabled = Boolean.parseBoolean(arg.substring(arg.indexOf('=') + 1));
                expectRuntimeArgs = false;
            } else {
                break;
            }
    ....

還記的我們傳入的參數(shù)是什么嗎专执?"1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n"淮捆,這里就將mUsapPoolStatusSpecified設(shè)置為truemUsapPoolEnabled設(shè)置為 mUsapPoolEnabled也為true本股。所以會(huì)執(zhí)行
handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled) => zygoteServer.setUsapPoolStatus:

    Runnable setUsapPoolStatus(boolean newStatus, LocalSocket sessionSocket) {
        if (!mUsapPoolSupported) {
            Log.w(TAG,
                    "Attempting to enable a USAP pool for a Zygote that doesn't support it.");
            return null;
        } else if (mUsapPoolEnabled == newStatus) {
            return null;
        }

        Log.i(TAG, "USAP Pool status change: " + (newStatus ? "ENABLED" : "DISABLED"));

        mUsapPoolEnabled = newStatus;

        if (newStatus) {
            return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() });
        } else {
            Zygote.emptyUsapPool();
            return null;
        }
    }

這里newStatus就是mUsapPoolEnabled攀痊,我們這里為true開啟UsapPool,如果這個(gè)值為false拄显,就是關(guān)閉UsapPool苟径。我們直接看fillUsapPool

    Runnable fillUsapPool(int[] sessionSocketRawFDs) {
        ....
        if (usapPoolCount < mUsapPoolSizeMin
                || numUsapsToSpawn >= mUsapPoolRefillThreshold) {
            ....

            while (usapPoolCount++ < mUsapPoolSizeMax) {
                Runnable caller = Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs);

                if (caller != null) {
                    return caller;
                }
            }
            ....
        }
        ....
        return null;
    }

這里判斷如果當(dāng)前USAP進(jìn)程小于最大USAP進(jìn)程,則調(diào)用Zygote.forkUsap凿叠,這里注意傳入的mUsapPoolSocket參數(shù),他在ZygoteServer構(gòu)造函數(shù)中初始化了:

mUsapPoolSocket = Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);

public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";

我們的AMS之后會(huì)通過這個(gè)USAP_POOL_PRIMARY_SOCKET_NAME創(chuàng)建LocalSocket與USAP進(jìn)程通信的嚼吞。
不過你可能意識(shí)到了盒件,這些fork出的進(jìn)程將會(huì)監(jiān)聽在同一個(gè)SocketServer上,這里就是一個(gè)技術(shù)細(xì)節(jié)了:

如果多個(gè)進(jìn)程或者線程在等待同一個(gè)事件舱禽,當(dāng)事件發(fā)生時(shí)炒刁,所有線程和進(jìn)程都會(huì)被內(nèi)核喚醒,喚醒后通常只有一個(gè)進(jìn)程獲得了該事件并進(jìn)行處理誊稚,其他進(jìn)程發(fā)現(xiàn)獲取事件失敗后又繼續(xù)進(jìn)入了等待狀態(tài)翔始,在一定程度上降低了系統(tǒng)性能,這稱為 驚群效應(yīng)里伯。
這里多個(gè) USAP 共同監(jiān)聽了同一個(gè) Socket城瞎,而在 Linux Kernel 2.6 后 Socket 的 accept() 通過維護(hù)一個(gè)等待隊(duì)列來解決這一問題,因此這段代碼中避免了驚群效應(yīng)

回到fillUsapPoolZygote.forkUsap函數(shù):

    static Runnable forkUsap(LocalServerSocket usapPoolSocket,
                             int[] sessionSocketRawFDs) {
        FileDescriptor[] pipeFDs = null;

        try {
            pipeFDs = Os.pipe2(O_CLOEXEC);
        } catch (ErrnoException errnoEx) {
            throw new IllegalStateException("Unable to create USAP pipe.", errnoEx);
        }

        int pid =
                nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);

        if (pid == 0) {
            IoUtils.closeQuietly(pipeFDs[0]);
            return usapMain(usapPoolSocket, pipeFDs[1]);
        } else {
            // The read-end of the pipe will be closed by the native code.
            // See removeUsapTableEntry();
            IoUtils.closeQuietly(pipeFDs[1]);
            return null;
        }
    }

這里通過底層函數(shù)fork出了新的進(jìn)程疾瓮。在子進(jìn)程中調(diào)用了usapMain:

    private static Runnable usapMain(LocalServerSocket usapPoolSocket,
                                     FileDescriptor writePipe) {
        ....

        while (true) {
            try {
                //等待socket客戶端的連接
                sessionSocket = usapPoolSocket.accept();
                ....

            } catch (Exception ex) {
                Log.e("USAP", ex.getMessage());
                IoUtils.closeQuietly(sessionSocket);

                // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
                unblockSigTerm();
            }
        }

        try {
            ....
            specializeAppProcess(args.mUid, args.mGid, args.mGids,
                                 args.mRuntimeFlags, rlimits, args.mMountExternal,
                                 args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                 args.mInstructionSet, args.mAppDataDir);


            if (args.mNiceName != null) {
                Process.setArgV0(args.mNiceName);
            }

            // End of the postFork event.
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

            return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
                                         args.mRemainingArgs,
                                         null /* classLoader */);
        } finally {
            // Unblock SIGTERM to restore the process to default behavior.
            unblockSigTerm();
        }
    }

這里調(diào)用usapPoolSocket.accept() 阻塞等待客戶端連接脖镀,這個(gè)客戶端就是是來自AMS的請(qǐng)求,后面有提到狼电。接收到請(qǐng)求之后specializeAppProcess將進(jìn)程''specialize''成app進(jìn)程蜒灰,然后調(diào)用我們熟悉的ZygoteInit.zygoteInit最終執(zhí)行到了ActivityThreadmain函數(shù)。

我們?cè)賮砜碅MS是如何和USAP進(jìn)程通信的肩碟,首先回到本文開始的ZygoteProcessstartViaZygote方法:

private Process.ProcessStartResult startViaZygote(....) throws ZygoteStartFailedEx {
        ....

        synchronized(mLock) {
            // The USAP pool can not be used if the application will not use the systems graphics
            // driver.  If that driver is requested use the Zygote application start path.
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                              useUsapPool,
                                              argsForZygote);
        }
    }

來看zygoteSendArgsAndGetResult

    private Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, boolean useUsapPool, @NonNull ArrayList<String> args)
            throws ZygoteStartFailedEx {

        ....

        if (useUsapPool && mUsapPoolEnabled && canAttemptUsap(args)) {
            try {
                return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
            } catch (IOException ex) {
                // If there was an IOException using the USAP pool we will log the error and
                // attempt to start the process through the Zygote.
                Log.e(LOG_TAG, "IO Exception while communicating with USAP pool - "
                        + ex.getMessage());
            }
        }

        return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr);
    }

如果使用USAP就調(diào)用attemptUsapSendArgsAndGetResult(zygoteState, msgStr)强窖,我們顯然是使用的情況:

    private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(
            ZygoteState zygoteState, String msgStr)
            throws ZygoteStartFailedEx, IOException {
        try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
            final BufferedWriter usapWriter =
                    new BufferedWriter(
                            new OutputStreamWriter(usapSessionSocket.getOutputStream()),
                            Zygote.SOCKET_BUFFER_SIZE);
            final DataInputStream usapReader =
                    new DataInputStream(usapSessionSocket.getInputStream());

            usapWriter.write(msgStr);
            usapWriter.flush();

            Process.ProcessStartResult result = new Process.ProcessStartResult();
            result.pid = usapReader.readInt();
            // USAPs can't be used to spawn processes that need wrappers.
            result.usingWrapper = false;

            if (result.pid >= 0) {
                return result;
            } else {
                throw new ZygoteStartFailedEx("USAP specialization failed");
            }
        }
    }

getUsapSessionSocket通過 mUsapSocketAddress創(chuàng)建LocalSocket():

        LocalSocket getUsapSessionSocket() throws IOException {
            final LocalSocket usapSessionSocket = new LocalSocket();
            usapSessionSocket.connect(this.mUsapSocketAddress);

            return usapSessionSocket;
        }

mUsapSocketAddress的值如下:

mUsapPoolSocketAddress =new LocalSocketAddress(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME,LocalSocketAddress.Namespace.RESERVED);

public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";

和上面說到的USAP阻塞等待的socket地址是一致的,這里就和上面的內(nèi)容接上了削祈〕崮纾回到attemptUsapSendArgsAndGetResult,socket連接成功后,獲取輸入輸出流未巫,并寫入?yún)?shù)窿撬,讀取USAP進(jìn)程的pid,和直接和Zygote進(jìn)程通信是一致的叙凡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劈伴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子握爷,更是在濱河造成了極大的恐慌跛璧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件新啼,死亡現(xiàn)場(chǎng)離奇詭異追城,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)燥撞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門座柱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人物舒,你說我怎么就攤上這事色洞。” “怎么了冠胯?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵火诸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我荠察,道長(zhǎng)置蜀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任悉盆,我火速辦了婚禮盯荤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焕盟。我一直安慰自己廷雅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布京髓。 她就那樣靜靜地躺著航缀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪堰怨。 梳的紋絲不亂的頭發(fā)上芥玉,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音备图,去河邊找鬼灿巧。 笑死赶袄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抠藕。 我是一名探鬼主播饿肺,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼盾似!你這毒婦竟也來了敬辣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤零院,失蹤者是張志新(化名)和其女友劉穎溉跃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體告抄,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撰茎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了打洼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龄糊。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖募疮,靈堂內(nèi)的尸體忽然破棺而出炫惩,到底是詐尸還是另有隱情,我是刑警寧澤酝锅,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布诡必,位于F島的核電站奢方,受9級(jí)特大地震影響搔扁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蟋字,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一稿蹲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鹊奖,春花似錦苛聘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至两蟀,卻和暖如春网梢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赂毯。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工战虏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拣宰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓烦感,卻偏偏與公主長(zhǎng)得像巡社,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子手趣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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