当前位置:
首页
文章
移动端
详情

Android USAP 进程启动流程

从Android Q(10)开始,Google引入了一种新的机制,加快了app的启动时间,具体请看Android Framework | 一种新型的应用启动机制:USAP,本篇将会详细介绍USAP 进程启动的流程。

从Activity启动流程 上篇(Android 10),我们得知在Activity启动过程中,我们会调用到\frameworks\base\core\java\android\os\ZygoteProcess.javastart方法,然后调用startViaZygote(),其实在调用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);
        }
    }

从函数名称,我们可以得知,这里用fetchUsapPoolEnabledPropWithMinInterval判断系统是否开启了USAP功能,如果开启则调用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);
            }
        }
    }

可以看到该函数将调用attemptConnectionToPrimaryZygoteattemptConnectionToSecondaryZygote()这其实和Zygote()运行的位数有关,32或者64位,他们都调用了ZygoteState.connect,只是传入的参数不同,我们看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));
        }

这段代码比较好理解,就是创建一个LocalSocketZygote建立连接,并获取输入输出流设置到ZygoteState中,待会儿我们会用到,至此attemptConnectionToPrimaryZygote调用完成,回到上面的informZygotesOfUsapPoolStatus,代码将command写给了Zygote,而command"1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n",这里mUsapPoolEnabled自然为true, 记住这个command,接下来可以到zygote进程中查看了。

在Android系统启动流程末尾,我们说到Zygote进程会执行zygoteServer.runSelectLoop(abiList),接收并处理AMS传过来的消息,比如fork app进程。这里我们直接看runSelectLoop函数即可:

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会使用epoll机制,阻塞在Os.poll(pollFDs, -1),获取对方连接请求后,执行\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;
            }
    ....

还记的我们传入的参数是什么吗?"1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n",这里就将mUsapPoolStatusSpecified设置为truemUsapPoolEnabled设置为 mUsapPoolEnabled也为true。所以会执行
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,如果这个值为false,就是关闭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;
    }

这里判断如果当前USAP进程小于最大USAP进程,则调用Zygote.forkUsap,这里注意传入的mUsapPoolSocket参数,他在ZygoteServer构造函数中初始化了:

mUsapPoolSocket = Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);

public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";

我们的AMS之后会通过这个USAP_POOL_PRIMARY_SOCKET_NAME创建LocalSocket与USAP进程通信的。
不过你可能意识到了,这些fork出的进程将会监听在同一个SocketServer上,这里就是一个技术细节了:

如果多个进程或者线程在等待同一个事件,当事件发生时,所有线程和进程都会被内核唤醒,唤醒后通常只有一个进程获得了该事件并进行处理,其他进程发现获取事件失败后又继续进入了等待状态,在一定程度上降低了系统性能,这称为 惊群效应。
这里多个 USAP 共同监听了同一个 Socket,而在 Linux Kernel 2.6 后 Socket 的 accept() 通过维护一个等待队列来解决这一问题,因此这段代码中避免了惊群效应

回到fillUsapPoolZygote.forkUsap函数:

    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;
        }
    }

这里通过底层函数fork出了新的进程。在子进程中调用了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();
        }
    }

这里调用usapPoolSocket.accept() 阻塞等待客户端连接,这个客户端就是是来自AMS的请求,后面有提到。接收到请求之后specializeAppProcess将进程''specialize''成app进程,然后调用我们熟悉的ZygoteInit.zygoteInit最终执行到了ActivityThreadmain函数。

我们再来看AMS是如何和USAP进程通信的,首先回到本文开始的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就调用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创建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地址是一致的,这里就和上面的内容接上了。回到attemptUsapSendArgsAndGetResult,socket连接成功后,获取输入输出流,并写入参数,读取USAP进程的pid,和直接和Zygote进程通信是一致的。

免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。