Android系統原始碼分析--Process啟動過程

墨香發表於2017-09-14

由於四大元件的啟動都涉及到程式的啟動,因此我們這章先講一下程式啟動流程,然後再講四大元件的啟動流程。

基礎知識

Android應用程式框架層建立的應用程式程式具有兩個特點,一是程式的入口函式是ActivityThread.main,二是程式天然支援Binder程式間通訊機制;這兩個特點都是在程式的初始化過程中實現的。(引用自老羅安卓之旅-Android應用程式程式啟動過程的原始碼分析

程式按照重要性可以分為下面五類:

  • 前臺程式(Foreground process)
  • 可見程式(Visible process)
  • 服務程式(Service process)
  • 後臺程式(Background process)
  • 空程式(Empty process)

程式啟動流程

AMS(ActivityMagagerService)啟動程式是從其成員函式startProcessLocked開始呼叫Process.start方法開始的。我們先看一下程式啟動的時序圖:

1. Process.start方法:

public static final ProcessStartResult start(final String processClass,
                                                 final String niceName,
                                                 int uid, int gid, int[] gids,
                                                 int debugFlags, int mountExternal,
                                                 int targetSdkVersion,
                                                 String seInfo,
                                                 String abi,
                                                 String instructionSet,
                                                 String appDataDir,
                                                 String[] zygoteArgs) {
    try {
        // 請求Zygote程式建立一個應用程式
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
     } catch (ZygoteStartFailedEx ex) {
        Log.e(LOG_TAG,
                "Starting VM process through Zygote failed");
        throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
     }
}複製程式碼

注意:傳入的第一個引數是“android.app.ActivityThread”,這是程式初始化要載入的類,這個類載入到程式之後,就會把這個類的靜態成員方法main作為程式的入口。然後呼叫startViaZygote方法。

2. startViaZygote方法:

private static ProcessStartResult startViaZygote(final String processClass,
                                                     final String niceName,
                                                     final int uid, final int gid,
                                                     final int[] gids,
                                                     int debugFlags, int mountExternal,
                                                     int targetSdkVersion,
                                                     String seInfo,
                                                     String abi,
                                                     String instructionSet,
                                                     String appDataDir,
                                                     String[] extraArgs)
            throws ZygoteStartFailedEx {
        synchronized (Process.class) {
            ArrayList<String> argsForZygote = new ArrayList<String>();

            // 儲存要建立應用程式程式的啟動引數到argsForZygote中
            ...

            // 儲存id到argsForZygote中
            ...

            // 儲存其他資訊到argsForZygote中
            ...

            // 請求Zygote程式建立這個應用程式
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }複製程式碼

這個方法主要是儲存資訊到argsForZygote中,然後呼叫openZygoteSocketIfNeeded,然後根據返回的值呼叫zygoteSendArgsAndGetResult方法,首先先看openZygoteSocketIfNeeded方法。

3. openZygoteSocketIfNeeded方法:

private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                // 通過呼叫ZygoteState.connect方法建立LocalSocket物件,以便將相應引數傳入Zygote程式
                primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
        }

        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
        }

        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
                // 通過呼叫ZygoteState.connect方法建立LocalSocket物件,以便將相應引數傳入Zygote程式
                secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
        }

        if (secondaryZygoteState.matches(abi)) {
            return secondaryZygoteState;
        }

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }複製程式碼

通過ZygoteState.connect放建立primaryZygoteState物件,如果第一次建立不成功,建立第二次。connect方法程式碼如下:

4. ZygoteState.connect方法:

public static ZygoteState connect(String socketAddress) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            // 這個Socket由ZygoteInit.java檔案中的ZygoteInit類在runSelectLoopMode函式偵聽的。
            final LocalSocket zygoteSocket = new LocalSocket();

            try {
                // 開始建立連線,在連線過程中,LocalSocket物件zygoteSocket會在/dev/socket目錄下找到
                // 一個對應的zygote檔案,然後將它與自己繫結起來,這就相當於與Zygote程式中的名稱為“zygote”
                // 的Socket建立了連線
                zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                        LocalSocketAddress.Namespace.RESERVED));

                // 連線成功以後,首先獲取LocalSocket物件zygoteSocket的一個輸入流,並且儲存在
                // zygoteInputStream中,以便獲得Zygote程式傳送過來的通訊資料
                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());

                // 又得到LocalSocket物件zygoteSocket的一個輸入流,並且儲存在zygoteWriter中,以便
                // 可以向Zygote程式傳送通訊資料
                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                ...
            }

            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
            Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);

            // 建立的LocalSocket物件zygoteSocket會儲存在ZygoteState中
            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }複製程式碼

首先建立一個LocalSocket物件,這個LocalSocket物件是在ZygoteInit中的runSelectLoop函式進行監聽的。然後通過connect方法並且傳入連線地址連線該Socket,連線以後會獲取輸入流DataInputStream,以便獲得Zygote程式傳送過來的通訊資料,然後又獲取BufferedWriter輸入流,以便向Zygote程式傳送通訊資料。最後會返回一個ZygoteState物件。下面我們看一下LocalSocket.connect方法。

5. LocalSocket.connect方法:

    public void connect(LocalSocketAddress endpoint) throws IOException {
        synchronized (this) {
            if (isConnected) {
                throw new IOException("already connected");
            }

            implCreateIfNeeded();
            impl.connect(endpoint, 0);
            isConnected = true;
            isBound = true;
        }
    }複製程式碼

如果已經連線,丟擲異常,因為連線完成後,會關閉連線,使用時在開啟連線。最後呼叫native方法連線socket,並且改變連線標籤。

6. 回到第二步,呼叫完openZygoteSocketIfNeeded返回引數ZygoteState傳入到zygoteSendArgsAndGetResult方法中:

    private static ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            throws ZygoteStartFailedEx {
        try {
            // Throw early if any of the arguments are malformed. This means we can
            // avoid writing a partial response to the zygote.
            int sz = args.size();
            for (int i = 0; i < sz; i++) {
                if (args.get(i).indexOf('\n') >= 0) {
                    throw new ZygoteStartFailedEx("embedded newlines not allowed");
                }
            }

            final BufferedWriter writer = zygoteState.writer;
            final DataInputStream inputStream = zygoteState.inputStream;

            writer.write(Integer.toString(args.size()));
            writer.newLine();

            for (int i = 0; i < sz; i++) {
                String arg = args.get(i);
                writer.write(arg);
                writer.newLine();
            }

            writer.flush();
            // Zygote程式接收到這些資料之後,就會建立一個新的應用程式程式,並且將這個新建立的應用程式程式
            // 的PID返回給Activity管理服務AMS

            // Should there be a timeout on this?
            ProcessStartResult result = new ProcessStartResult();

            // Always read the entire result from the input stream to avoid leaving
            // bytes in the stream for future process starts to accidentally stumble
            // upon.
            result.pid = inputStream.readInt();
            result.usingWrapper = inputStream.readBoolean();

            if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }
            return result;
        } catch (IOException ex) {
            zygoteState.close();
            throw new ZygoteStartFailedEx(ex);
        }
    }複製程式碼

這方法通過Socket流的方式將啟動程式的資訊傳送出去,從步驟4可知,這個Socket的監聽是ZygoteInit類中的runSelectLoop方法,我們接著看這個方法。

7. ZygoteInit.runSelectLoop方法:

    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);
                    }
                }
            }
        }
    }複製程式碼

資料通過Socket傳送以後,Zygote程式接收到後會呼叫peers.get(i).runOnce()方法。這個peers.get(i)是獲取ZygoteConnection物件,表示一個Socket連線,然後呼叫它的runOnce方法。

8. ZygoteConnection.runOnce方法:

    boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {

        String args[];
        Arguments parsedArgs = null;
        FileDescriptor[] descriptors;

        try {
            // 獲得建立應用程式程式需要的啟動引數,並且儲存在一個Arguments物件parsedArgs中
            args = readArgumentList();
            descriptors = mSocket.getAncillaryFileDescriptors();
        } catch (IOException ex) {
            Log.w(TAG, "IOException on command socket " + ex.getMessage());
            closeSocket();
            return true;
        }

        ...

        /** the stderr of the most recent request, if avail */
        PrintStream newStderr = null;

        if (descriptors != null && descriptors.length >= 3) {
            newStderr = new PrintStream(
                    new FileOutputStream(descriptors[2]));
        }

        int pid = -1;
        FileDescriptor childPipeFd = null;
        FileDescriptor serverPipeFd = null;

        try {
            parsedArgs = new Arguments(args);

            if (parsedArgs.abiListQuery) {
                return handleAbiListQuery();
            }

            ...

            // 呼叫forkAndSpecialize方法來建立這個應用程式程式,最終通過函式fork在當前程式中建立一個子程式,
            // 因此,當它的返回值等於0時,就表示是在新建立的子程式中執行的,這時候ZygoteConnection類就會呼叫
            // 成員函式handleChildProc來啟動這個子程式
            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) {
            ...
        } catch (IllegalArgumentException ex) {
            ...
        } catch (ZygoteSecurityException ex) {
            ...
        }

        try {
            if (pid == 0) {
                ...
                handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);

                return true;
            } else {
                ...
                return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
            }
        } finally {
            ...
        }
    }複製程式碼

首先通過Zygote.forkAndSpecialize方法來建立一個新的程式,並且返回其pid。因為我們在分心新建程式,因此我們只分析pid為0的情況,pid為0時會呼叫handleChildProc方法,

9. handleChildProc方法:

    private void handleChildProc(Arguments parsedArgs,
            FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
            throws ZygoteInit.MethodAndArgsCaller {
        ...

        // End of the postFork event.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        if (parsedArgs.invokeWith != null) {
            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    VMRuntime.getCurrentInstructionSet(),
                    pipeFd, parsedArgs.remainingArgs);
        } else {
            // 初始化執行庫以及啟動一個Binder執行緒池
            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                    parsedArgs.remainingArgs, null /* classLoader */);
        }
    }複製程式碼

由於我們之前加入引數是沒有parsedArgs.invokeWith這個引數,因此這裡是null,因此會走else裡面的程式碼,執行RuntimeInit.zygoteInit方法。

10. RuntimeInit.zygoteInit方法:

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

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
        redirectLogStreams();

        // 首先呼叫下面函式來設定新建立的應用程式程式的時區和鍵盤佈局等通用資訊
        commonInit();
        // 然後呼叫下面Native函式在新建立的應用程式程式中啟動一個Binder執行緒池
        nativeZygoteInit();
        applicationInit(targetSdkVersion, argv, classLoader);
    }複製程式碼

首先呼叫nativeZygoteInit函式,這是一個native函式,函式的目的是在新建立的應用程式程式中啟動一個Binder執行緒池然後進行程式間通訊。然後呼叫applicationInit函式

11. applicationInit函式:

    private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        ...

        // Remaining arguments are passed to the start class's static main
        // 我們知道AMS指定了新建立的應用程式程式的入口函式為ActivityThread類的靜態成員函式main。實際是
        // 通過下面方法進入到ActivityThread類的靜態成員函式main中的
        invokeStaticMain(args.startClass, args.startArgs, classLoader);
    }複製程式碼

我們在前面講過args.startClass傳入進來的是"android.app.ActivityThread",表示要執行"android.app.ActivityThread"的main函式。

12. 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 {
            // 獲取它的靜態成員函式main,並且儲存在Method物件m中
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            ...
        } catch (SecurityException ex) {
            ...
        }

        ...

        /*
         * 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.
         * 將這個Method物件封裝在一個MethodAndArgsCaller物件中,並且將這個MethodAndArgsCaller物件作為
         * 一個異常物件丟擲來給當前應用程式處理
         */
        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
        /**
         * 引用自Android系統原始碼情景分析中的Android程式啟動分析一文
         * 新建立的應用程式程式複製了Zygote程式的地址空間,因此,當前新建立的應用程式程式的呼叫棧與Zygote
         * 程式的呼叫堆疊是一致的。Zygote程式最開始執行的是應用程式app_process的入口函式main,接著再呼叫
         * ZygoteInit類的靜態成員函式main,最後進入到ZygoteInit類的靜態成員函式runSelectLoopMode來迴圈
         * 等待Activity管理服務AMS傳送過來的建立新的應用程式的請求。當Zygote程式收到AMS傳送過來的建立新的
         * 應用程式程式的請求之後,它就會建立一個新的應用程式程式,並且讓這個新建立的應用程式程式沿著
         * ZygoteInit類的靜態函式runSelectLoopModel一直執行到RuntimeInit類的靜態成員函式
         * invokeStaticMain。因此,當RuntimeInit類的靜態成員函式invokeStaticMain丟擲一個型別為
         * MethodAndArgsCaller的常時,系統就會沿著這個呼叫過程往後找到一個適合的程式碼塊來捕獲它。
         * 由於ZygoteInit函式main捕獲了型別為MethodAndArgsCaller的異常,因此,接下來它就會被呼叫,以便
         * 可以處理這裡丟擲的一個MethodAndArgsCaller異常。因此,丟擲這個異常後,會執行ZygoteInit中main
         * 函式中的catch來捕獲異常。
         *
         */
    }複製程式碼

這個就是通過類載入器載入ActivityThread,然後呼叫起main方法。然後丟擲異常,通過ZygoteInit中main函式中的catch來捕獲異常。

13. ZygoteInit.main函式:

    public static void main(String argv[]) {
        ...
        } catch (MethodAndArgsCaller caller) {
            // 捕獲MethodAndArgsCaller異常以後會呼叫MethodAndArgsCaller的run函式
            // ActivityThread.main
            caller.run();
        } catch (Throwable ex) {
            ...
        }
    }複製程式碼

通過步驟12可知丟擲的異常是MethodAndArgsCaller異常,因此會執行caller.run方法。

14. MethodAndArgsCaller.run:

        /**
         * 註釋來自Android系統原始碼情景分析
         * 這裡開始呼叫ActivityThread.main方法,為什麼要繞這麼遠呢,前面提到,AMS請求Zygote程式建立的應用
         * 程式程式的入口函式為ActivityThread的main函式,但是由於新建立的應用程式程式一開始就需要再內部初始
         * 化執行時庫,以及啟動Binder執行緒池,因此,ActivityThread的main函式被呼叫時,新建立的應用程式程式
         * 實際上已經執行了相當多的程式碼,為了使得西建立的應用程式的程式覺得它的入口函式就是ActivityThread類
         * 的main函式,系統就不能直接呼叫,而是丟擲異常回到ZygoteInit的main函式中,然後間接呼叫它,這樣就
         * 可以巧妙的利用Java語言的異常處理來清理它前面呼叫的堆疊了
         */
        public void run() {
            try {
                // 呼叫ActivityThread.main
                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方法呼叫ActivityThread的main方法。

15. ActivityThread.mian方法:

    /**
     * 啟動新的程式時呼叫Process的start方法會最終呼叫改函式
     * 啟動新的程式主要做了兩件事:
     * 1.在程式中建立了一個ActivityThread物件,並呼叫了它的成員函式attach向AMS傳送一個啟動完成的通知
     * 2.呼叫Looper類的靜態成員函式prepareMainLooper建立一個訊息迴圈,並且在向AMS傳送啟動完成通知後,
     *   使得當前程式進入到這個訊息迴圈中
     *
     * @param args
     */
    public static void main(String[] args) {
        ...

        // 建立looper
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        // 傳入false表示非系統程式啟動
        thread.attach(false);

        if (sMainThreadHandler == null) {
            // 獲取主執行緒的Handler
            sMainThreadHandler = thread.getHandler();
        }

        ...

        // 開始無限迴圈
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }複製程式碼

這裡主要是建立該執行緒的looper,然後建立ActivityThread物件,然後進入訊息迴圈。然後我們就可以啟動Activity或者Service了。

Android開發群:192508518

微信公眾賬號:Code-MX

注:本文原創,轉載請註明出處,多謝。

相關文章