Android 重學(xué)系列--系統(tǒng)啟動(dòng)到Activity(下)

Zygote 進(jìn)程間通信原理

不熟悉Linux編程的同學(xué)看到死循環(huán)最后這一段,可能就有點(diǎn)懵欢嘿。這里我解釋一遍密似,在構(gòu)造一下整個(gè)流程以及模型估計(jì)就能明白了吗垮。

雖然是socket通信,但是實(shí)際上和我們常說(shuō)Java的socket編程稍微有一點(diǎn)點(diǎn)不一樣织堂。實(shí)際上更加像驅(qū)動(dòng)中的文件描述的監(jiān)聽(tīng)叠艳。這里和Android4.4的有點(diǎn)不一樣,但是思路是一樣捧挺。

Zygote監(jiān)聽(tīng)服務(wù)端

從上面的代碼虑绵,根據(jù)我的理論,peers這個(gè)ZygoteConnection是一個(gè)Zygote的鏈接對(duì)象闽烙,用來(lái)處理從遠(yuǎn)端的socket過(guò)來(lái)的消息翅睛。這個(gè)是一個(gè)關(guān)鍵類(lèi)。我們看看這個(gè)ZygoteConnection究竟是怎么構(gòu)造的黑竞。

    private static ZygoteConnection acceptCommandPeer(String abiList) {
        try {
            return new ZygoteConnection(sServerSocket.accept(), abiList);
        } catch (IOException ex) {
            throw new RuntimeException(
                    "IOException during accept()", ex);
        }
    }

實(shí)際上此處會(huì)new一個(gè)ZygoteConnection捕发,會(huì)把LocalServerSocket的accpet傳進(jìn)去。此時(shí)就和普通的socket一樣進(jìn)入阻塞很魂。

讓我先把LocalSocket這一系列的UML圖放出來(lái)就能明白扎酷,這幾個(gè)類(lèi)之間關(guān)系。


LocalSocket uml.png

實(shí)際上遏匆,所有的LocalSocket法挨,無(wú)論是服務(wù)端LocalServerSocket還是客戶(hù)端LocalSocket都是通過(guò)LocalServerImpl實(shí)現(xiàn)的谁榜。

protected void accept(LocalSocketImpl s) throws IOException {
        if (fd == null) {
            throw new IOException("socket not created");
        }

        try {
            s.fd = Os.accept(fd, null /* address */);
            s.mFdCreatedInternally = true;
        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
        }
    }

這個(gè)Os對(duì)象通過(guò)Libcore.os.accept(fd, peerAddress);調(diào)用native層。
文件:/libcore/luni/src/main/native/libcore_io_Posix.cpp

static jobject Posix_accept(JNIEnv* env, jobject, jobject javaFd, jobject javaSocketAddress) {
    sockaddr_storage ss;
    socklen_t sl = sizeof(ss);
    memset(&ss, 0, sizeof(ss));
//判斷java層的socket對(duì)象是否為NULL
    sockaddr* peer = (javaSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
    socklen_t* peerLength = (javaSocketAddress != NULL) ? &sl : 0;

//核心凡纳,此處等待阻塞線(xiàn)程
    jint clientFd = NET_FAILURE_RETRY(env, int, accept, javaFd, peer, peerLength);
    if (clientFd == -1 || !fillSocketAddress(env, javaSocketAddress, ss, *peerLength)) {
        close(clientFd);
        return NULL;
    }
//一旦socket回調(diào)之后窃植,將會(huì)通過(guò)底層的fd對(duì)象轉(zhuǎn)化為java對(duì)象
    return (clientFd != -1) ? jniCreateFileDescriptor(env, clientFd) : NULL;
}

此處分為三步:

  • 第一步,通過(guò)解析address是否為空荐糜,來(lái)決定阻塞的等待時(shí)長(zhǎng)巷怜,此時(shí)傳下來(lái)為null,為無(wú)限期的等待暴氏。
  • 第二步延塑,核心方法,通過(guò)define聲明的NET_FAILURE_RETRY代碼段答渔,阻塞線(xiàn)程
  • 第三步关带,一旦等待的socket鏈接有數(shù)據(jù)回調(diào)進(jìn)來(lái),則轉(zhuǎn)化為java層的fd返回沼撕。

此處是阻塞的核心代碼

#define NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \
    return_type _rc = -1; \
    int _syscallErrno; \
    do { \
        bool _wasSignaled; \
        { \
//轉(zhuǎn)化java的fd豫缨,對(duì)Java進(jìn)行監(jiān)聽(tīng)
            int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \
            AsynchronousCloseMonitor _monitor(_fd); \
            _rc = syscall_name(_fd, __VA_ARGS__); \
            _syscallErrno = errno; \
            _wasSignaled = _monitor.wasSignaled(); \
        } \
        if (_wasSignaled) { \
            jniThrowException(jni_env, "java/net/SocketException", "Socket closed"); \
            _rc = -1; \
            break; \
        } \
        if (_rc == -1 && _syscallErrno != EINTR) { \
            /* TODO: with a format string we could show the arguments too, like strace(1). */ \
            throwErrnoException(jni_env, # syscall_name); \
            break; \
        } \
    } while (_rc == -1); /* _syscallErrno == EINTR && !_wasSignaled */ \
    if (_rc == -1) { \
        /* If the syscall failed, re-set errno: throwing an exception might have modified it. */ \
        errno = _syscallErrno; \
    } \
    _rc; })

這里稍微解釋一下,這段阻塞的核心方法的意思端朵。
這循環(huán)代碼的跳出條件有三個(gè):

  • _wasSignaled 為true 也就是說(shuō)此時(shí)AsynchronousCloseMonitor通過(guò)線(xiàn)程鎖ScopeThreadMutex上鎖的線(xiàn)程被喚醒好芭,說(shuō)明了該socket斷開(kāi),也就斷開(kāi)了阻塞冲呢。

  • _rc 為-1 以及 _syscallErrno 錯(cuò)誤標(biāo)示位不為EINTER舍败。rc為syscall_name(此時(shí)傳進(jìn)來(lái)的是socket的accept方法)。也就是說(shuō)當(dāng)accept鏈接出現(xiàn)異常的時(shí)候(返回-1)會(huì)一直在循環(huán)里面等待敬拓,除非為全局錯(cuò)誤_syscallErrno 不是系統(tǒng)拋出的中斷邻薯,則拋出異常。

  • 當(dāng)_rc不為-1乘凸,也就是說(shuō)socket鏈接成功厕诡。則繼續(xù)向下走。

因此從這里可以知道营勤,Zygote在初始化runSelectLoop的時(shí)候灵嫌,一開(kāi)始會(huì)加入一個(gè)ZygoteConnection用于阻塞監(jiān)聽(tīng)。一旦有鏈接進(jìn)來(lái)葛作,則喚醒則加入到peers隊(duì)列中寿羞。在死循環(huán)下一個(gè)輪回的時(shí)候,通過(guò)執(zhí)行runOnce執(zhí)行fork新的進(jìn)程赂蠢。

雖然到這里似乎就完成整個(gè)流程了绪穆。但是實(shí)際上,google工程師寫(xiě)代碼才不會(huì)這么簡(jiǎn)單就完成,而是做了一定的優(yōu)化玖院。

如果用Linux c寫(xiě)過(guò)服務(wù)器的哥們菠红,就會(huì)明白這樣不斷的阻塞只會(huì)不斷的消耗的cpu的資源,并不是很好的選擇难菌。

因此途乃,runSelectLoop才有這一段代碼

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

根據(jù)這段代碼,從表面上可以清楚的知道扔傅,一開(kāi)始把描述符都設(shè)置進(jìn)去StructPollfd等長(zhǎng)數(shù)組中。把這個(gè)數(shù)組交給Os.poll中烫饼。

我們先看看StructPollfd這個(gè)類(lèi)是個(gè)什么存在猎塞。
文件/libcore/luni/src/main/java/android/system/StructPollfd.java

public final class StructPollfd {
  /** The file descriptor to poll. */
  public FileDescriptor fd;

  /**
   * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
   * POLLOUT to the write fd set.
   */
  public short events;

  /** The events that actually happened. */
  public short revents;

  /**
   * A non-standard extension that lets callers conveniently map back to the object
   * their fd belongs to. This is used by Selector, for example, to associate each
   * FileDescriptor with the corresponding SelectionKey.
   */
  public Object userData;

  @Override public String toString() {
    return Objects.toString(this);
  }
}

這個(gè)類(lèi)十分簡(jiǎn)單。里面只有那么3個(gè)參數(shù)杠纵,events荠耽,revents,fd.分別是做什么的呢比藻?

我們直接看看Os.poll方法底層的實(shí)現(xiàn)
文件:/libcore/luni/src/main/native/libcore_io_Posix.cpp

static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint timeoutMs) {

//反射獲取structPollfd.java屬性的屬性id
    static jfieldID fdFid = env->GetFieldID(JniConstants::structPollfdClass, "fd", "Ljava/io/FileDescriptor;");
    static jfieldID eventsFid = env->GetFieldID(JniConstants::structPollfdClass, "events", "S");
    static jfieldID reventsFid = env->GetFieldID(JniConstants::structPollfdClass, "revents", "S");
//轉(zhuǎn)化為ndk底層的文件描述符
    // Turn the Java android.system.StructPollfd[] into a C++ struct pollfd[].
    size_t arrayLength = env->GetArrayLength(javaStructs);
    std::unique_ptr<struct pollfd[]> fds(new struct pollfd[arrayLength]);
    memset(fds.get(), 0, sizeof(struct pollfd) * arrayLength);
    size_t count = 0; // Some trailing array elements may be irrelevant. (See below.)
    for (size_t i = 0; i < arrayLength; ++i) {
        ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i));
        if (javaStruct.get() == NULL) {
            break; // We allow trailing nulls in the array for caller convenience.
        }
        ScopedLocalRef<jobject> javaFd(env, env->GetObjectField(javaStruct.get(), fdFid));
        if (javaFd.get() == NULL) {
            break; // We also allow callers to just clear the fd field (this is what Selector does).
        }
        fds[count].fd = jniGetFDFromFileDescriptor(env, javaFd.get());
        fds[count].events = env->GetShortField(javaStruct.get(), eventsFid);
        ++count;
    }

    std::vector<AsynchronousCloseMonitor*> monitors;
    for (size_t i = 0; i < count; ++i) {
        monitors.push_back(new AsynchronousCloseMonitor(fds[i].fd));
    }
//循環(huán)監(jiān)聽(tīng)
    int rc;
    while (true) {
        timespec before;
        clock_gettime(CLOCK_MONOTONIC, &before);
//poll 阻塞進(jìn)程
        rc = poll(fds.get(), count, timeoutMs);
        if (rc >= 0 || errno != EINTR) {
            break;
        }

        // We got EINTR. Work out how much of the original timeout is still left.
        if (timeoutMs > 0) {
            timespec now;
            clock_gettime(CLOCK_MONOTONIC, &now);

            timespec diff;
            diff.tv_sec = now.tv_sec - before.tv_sec;
            diff.tv_nsec = now.tv_nsec - before.tv_nsec;
            if (diff.tv_nsec < 0) {
                --diff.tv_sec;
                diff.tv_nsec += 1000000000;
            }

            jint diffMs = diff.tv_sec * 1000 + diff.tv_nsec / 1000000;
            if (diffMs >= timeoutMs) {
                rc = 0; // We have less than 1ms left anyway, so just time out.
                break;
            }

            timeoutMs -= diffMs;
        }
    }

    for (size_t i = 0; i < monitors.size(); ++i) {
        delete monitors[i];
    }
    if (rc == -1) {
        throwErrnoException(env, "poll");
        return -1;
    }
//喚醒之后更新runSelectLooper中的revents標(biāo)識(shí)位铝量,revents
    // Update the revents fields in the Java android.system.StructPollfd[].
    for (size_t i = 0; i < count; ++i) {
        ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i));
        if (javaStruct.get() == NULL) {
            return -1;
        }
        env->SetShortField(javaStruct.get(), reventsFid, fds[i].revents);
    }
    return rc;
}

這個(gè)代碼做了三件事情:

  • 1.通過(guò)反射獲取structPollfd.java中fd屬性,revents银亲,events屬性慢叨。把這些參數(shù)設(shè)置到pollfd[] fds隊(duì)列中。
    1. 把fds設(shè)置到poll進(jìn)行監(jiān)聽(tīng)
    1. 更新java層的structPollfd隊(duì)列务蝠。

核心是第二步驟拍谐,linux的poll的函數(shù)。
而poll函數(shù)的作用就是如果沒(méi)有檢測(cè)到文件描述符的變化馏段,則進(jìn)程進(jìn)入到睡眠狀態(tài)轩拨,等到有人喚醒。由于此時(shí)傳入的timeout為0院喜,則不設(shè)置超時(shí)等待時(shí)間亡蓉。

那么我們可以清楚的知道了,structPollfd做三個(gè)屬性是什么喷舀。

  • 第一個(gè)文件描述符砍濒,用來(lái)poll監(jiān)聽(tīng)該文件描述符是否出現(xiàn)了變化。在這里還記得硫麻,傳入的是zygote的socket文件嗎梯影?也就是說(shuō)此時(shí)poll在監(jiān)聽(tīng)socket是否出現(xiàn)了變化。

  • 第二個(gè)event庶香,作為pollfd中事件掩碼的參數(shù)

  • 第三個(gè)revent甲棍,代表了該文件描述符是否產(chǎn)生了變化。

因此,在每一次調(diào)用完Os.poll之后,如果socket有喚醒之后感猛,會(huì)更新StructPollfd中的數(shù)據(jù)七扰,也就有了下面這段判斷邏輯

            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
...
             }

喚醒之后直接循環(huán)pollFds中,判斷revents是否有變化陪白,和POLLIN(實(shí)際上是0)相于不為0則表示socket文件變化了颈走,才有下面的加入peers列表以及通過(guò)runOnce啟動(dòng)進(jìn)程。

通過(guò)這樣的優(yōu)化咱士,就能做到立由,當(dāng)沒(méi)有socket接入的時(shí)候,進(jìn)程休眠序厉,騰出了cpu資源锐膜。當(dāng)socket接入,則喚醒進(jìn)程弛房,進(jìn)入到accept道盏,等待數(shù)據(jù)的接入。這樣就能大大的提升了其中的資源利用率文捶。(一些普通的web服務(wù)器也是如此的設(shè)計(jì)的)

這里只是解釋了LocalSocket的服務(wù)端荷逞。

Zygote 客戶(hù)端

實(shí)際上一般的ZygoteSocket的客戶(hù)端,一般為SystemServer中的ActivitymanagerService.

我們看看在A(yíng)ndroid 7.0中當(dāng)不存在對(duì)應(yīng)的應(yīng)用進(jìn)程時(shí)候粹排,會(huì)調(diào)用startProcessLocked方法中Process的start方法种远。
最終會(huì)調(diào)用

 public static final String ZYGOTE_SOCKET = "zygote";


    private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                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 {
            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);
    }

這里的核心會(huì)調(diào)用一次ZygoteState的connect方法。

        public static ZygoteState connect(String socketAddress) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            final LocalSocket zygoteSocket = new LocalSocket();

            try {
                zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                        LocalSocketAddress.Namespace.RESERVED));

                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());

                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                try {
                    zygoteSocket.close();
                } catch (IOException ignore) {
                }

                throw ex;
            }

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

            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }

此時(shí)會(huì)嘗試的通過(guò)zygoteSocket也就是LocalSocket 去連接名為zygote的socket顽耳。也就是我們最開(kāi)始初始化的在ZygoteInit中registerZygoteSocket的socket名字院促。

調(diào)用connect方法,喚醒Os.poll方法之后斧抱,再喚醒LocalServerSocket.accept方法常拓,在循環(huán)的下一個(gè),調(diào)用runOnce辉浦。

那么zygote又是怎么啟動(dòng)ActivityThread弄抬,這個(gè)應(yīng)用第一個(gè)啟動(dòng)的類(lèi)呢?

第一次看runOnce代碼的老哥可能會(huì)被這一行蒙蔽了:

ZygoteConnection newPeer = acceptCommandPeer(abiList);

實(shí)際上在ZygoteConnection中宪郊,這個(gè)abiList不起任何作用掂恕。真正起作用的是ZygoteConnection.runOnce中readArgumentList
方法。
文件/frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

private String[] readArgumentList()
            throws IOException {

        /**
         * See android.os.Process.zygoteSendArgsAndGetPid()
         * Presently the wire format to the zygote process is:
         * a) a count of arguments (argc, in essence)
         * b) a number of newline-separated argument strings equal to count
         *
         * After the zygote process reads these it will write the pid of
         * the child or -1 on failure.
         */

        int argc;

        try {
            String s = mSocketReader.readLine();

            if (s == null) {
                // EOF reached.
                return null;
            }
            argc = Integer.parseInt(s);
        } catch (NumberFormatException ex) {
            Log.e(TAG, "invalid Zygote wire format: non-int at argc");
            throw new IOException("invalid wire format");
        }

        // See bug 1092107: large argc can be used for a DOS attack
        if (argc > MAX_ZYGOTE_ARGC) {
            throw new IOException("max arg count exceeded");
        }

        String[] result = new String[argc];
        for (int i = 0; i < argc; i++) {
            result[i] = mSocketReader.readLine();
            if (result[i] == null) {
                // We got an unexpected EOF.
                throw new IOException("truncated request");
            }
        }

        return result;
    }

看吧實(shí)際上所有的字符串都是通過(guò)zygote的SocketReader讀取出來(lái)弛槐,再賦值給上層懊亡。進(jìn)行fork出新的進(jìn)程。
在A(yíng)ctivityManagerService的startProcessLocked

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

第一個(gè)參數(shù)就是ActivityThread乎串,通過(guò)start方法店枣,來(lái)打runOnce之后,進(jìn)去handleChildProc,把ActivityThread的main反射出來(lái)鸯两,開(kāi)始了Activity的初始化闷旧。而實(shí)際上Process.start方法就是一個(gè)socket往Zygote中寫(xiě)數(shù)據(jù)。

handleChildProc

文件:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

因此钧唐,當(dāng)fork之后忙灼,我們繼續(xù)回到ZygoteInit的handleChildProc子進(jìn)程處理。

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

        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);
                }
                newStderr = System.err;
            } 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) {
            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    VMRuntime.getCurrentInstructionSet(),
                    pipeFd, parsedArgs.remainingArgs);
        } else {
            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                    parsedArgs.remainingArgs, null /* classLoader */);
        }
    }

子進(jìn)程將關(guān)閉socket钝侠,關(guān)閉socket的觀(guān)測(cè)的文件描述符该园。這里就能完好的讓進(jìn)程的fdtable(文件描述符表)騰出更多的控件。接著走RuntimeInit.zygoteInit.接下來(lái)的邏輯就和SystemServer一樣帅韧。同樣是反射了main方法里初,nativeZygoteInit 同樣會(huì)為新的App綁定繼承新的Binder底層loop,commonInit 為App的進(jìn)程初始化異常處理事件。我們可以來(lái)看看ActivityThread中的main方法弱匪。

文件:/frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
...

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

在這里面初始化了ActivityThread對(duì)象,并且傳入attach方法璧亮。

ActivityThread的綁定

private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
            // Watch for getting close to heap limit.
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                            mgr.releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                        }
                    }
                }
            });
        } else {
...
        }

        // add dropbox logging to libcore
        DropBox.setReporter(new DropBoxReporter());

        ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                synchronized (mResourcesManager) {
                    // We need to apply this change to the resources
                    // immediately, because upon returning the view
                    // hierarchy will be informed about it.
                    if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
                        // This actually changed the resources!  Tell
                        // everyone about it.
                        if (mPendingConfiguration == null ||
                                mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                            mPendingConfiguration = newConfig;
                            
                            sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                        }
                    }
                }
            }
            @Override
            public void onLowMemory() {
            }
            @Override
            public void onTrimMemory(int level) {
            }
        });
    }

實(shí)際上這里做的事情核心有兩個(gè):

  • 第一個(gè)把ApplictionThread綁定到AMS萧诫,讓之后我們startActivity能夠通過(guò)這個(gè)Binder對(duì)象找到對(duì)應(yīng)的方法,從而正確的執(zhí)行Activity中的正確的生命周期枝嘶。同時(shí)為Binder添加gc的監(jiān)聽(tīng)者帘饶。Binder的詳細(xì)情況會(huì)在Binder解析中了解到
  • 第二個(gè)就是為ViewRootImpl設(shè)置內(nèi)存管理。這個(gè)類(lèi)將會(huì)在后的view的繪制了解到群扶。

至此及刻,從Linux內(nèi)核啟動(dòng)到應(yīng)用的AcivityThread的大體流程就完成了。

優(yōu)化與思考

Android系統(tǒng)這么寫(xiě)Zygote孵化流程真的最佳的嗎竞阐?輝哥曾經(jīng)提問(wèn)過(guò)一個(gè)問(wèn)題缴饭,framework的啟動(dòng)流程該怎么優(yōu)化。

我們?nèi)シ?.4的整個(gè)流程和android 7.0做對(duì)比骆莹。發(fā)現(xiàn)除了加載虛擬機(jī)是從art變成dvm之外颗搂,其他邏輯大體上一致。

唯一不同的就是runSelectLoop方法出現(xiàn)了變化幕垦。
android 4.4.4
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

static final int GC_LOOP_COUNT = 10;

private static void runSelectLoop() throws MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
        FileDescriptor[] fdArray = new FileDescriptor[4];

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

        int loopCount = GC_LOOP_COUNT;
        while (true) {
            int index;

            /*
             * Call gc() before we block in select().
             * It's work that has to be done anyway, and it's better
             * to avoid making every child do it.  It will also
             * madvise() any free memory as a side-effect.
             *
             * Don't call it every time, because walking the entire
             * heap is a lot of overhead to free a few hundred bytes.
             */
//做一次gc為了給每個(gè)子進(jìn)程騰出內(nèi)存空間
            if (loopCount <= 0) {
                gc();
                loopCount = GC_LOOP_COUNT;
            } else {
                loopCount--;
            }

//每一次通過(guò)select檢測(cè)array中的fd有什么變化丢氢。
            try {
                fdArray = fds.toArray(fdArray);
                index = selectReadable(fdArray);
            } catch (IOException ex) {
                throw new RuntimeException("Error in select()", ex);
            }

//下面的邏輯一樣和之前的一樣
            if (index < 0) {
                throw new RuntimeException("Error in select()");
            } else if (index == 0) {
                ZygoteConnection newPeer = acceptCommandPeer();
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                boolean done;
                done = peers.get(index).runOnce();

                if (done) {
                    peers.remove(index);
                    fds.remove(index);
                }
            }
        }
    }

這里稍微解釋一下,在低版本fds和peers的意義還是沒(méi)有多少變動(dòng)先改,多了一個(gè)限制一次性最多也就4個(gè)ZygoteConnection監(jiān)聽(tīng)疚察。主要去看看下面的死循環(huán)之前的操作。

            try {
                fdArray = fds.toArray(fdArray);
                index = selectReadable(fdArray);
            } catch (IOException ex) {
                throw new RuntimeException("Error in select()", ex);
            }

這里面的代碼實(shí)際上和上面的Os.poll那一段的類(lèi)似仇奶。是為了監(jiān)聽(tīng)socket中哪些出現(xiàn)了變化貌嫡,而后喚醒進(jìn)程。
這個(gè)方法直接調(diào)用的是native方法。

static jint com_android_internal_os_ZygoteInit_selectReadable (
        JNIEnv *env, jobject clazz, jobjectArray fds)
{
...
    FD_ZERO(&fdset);
//獲取ndk層的fd
    int nfds = 0;
    for (jsize i = 0; i < length; i++) {
        jobject fdObj = env->GetObjectArrayElement(fds, i);
        if  (env->ExceptionOccurred() != NULL) {
            return -1;
        }
        if (fdObj == NULL) {
            continue;
        }
        int fd = jniGetFDFromFileDescriptor(env, fdObj);
        if  (env->ExceptionOccurred() != NULL) {
            return -1;
        }

        FD_SET(fd, &fdset);

        if (fd >= nfds) {
            nfds = fd + 1;
        }
    }
//select死循環(huán)阻塞
    int err;
    do {
        err = select (nfds, &fdset, NULL, NULL, NULL);
    } while (err < 0 && errno == EINTR);

    if (err < 0) {
        jniThrowIOException(env, errno);
        return -1;
    }
//查看哪些fd出現(xiàn)了變化衅枫,把index回調(diào)上去
    for (jsize i = 0; i < length; i++) {
        jobject fdObj = env->GetObjectArrayElement(fds, i);
        if  (env->ExceptionOccurred() != NULL) {
            return -1;
        }
        if (fdObj == NULL) {
            continue;
        }
        int fd = jniGetFDFromFileDescriptor(env, fdObj);
        if  (env->ExceptionOccurred() != NULL) {
            return -1;
        }
        if (FD_ISSET(fd, &fdset)) {
            return (jint)i;
        }
    }
    return -1;
}

這個(gè)函數(shù)分為三個(gè)部分:

    1. 從java層獲取fd的對(duì)象嫁艇,通過(guò)jniGetFDFromFileDescriptor轉(zhuǎn)化為具體的fd。每一次都加一個(gè)一,為select函數(shù)做準(zhǔn)備弦撩。
  • 2.調(diào)用select步咪,監(jiān)聽(tīng)所有的文件描述符中的變化
  • 3.尋找變化的文件描述符(socket)對(duì)應(yīng)的index,喚醒并且接受socket益楼。

如果不太懂Linux api select函數(shù)猾漫,這里放出一個(gè)寫(xiě)select的比較好的博文:
https://www.cnblogs.com/skyfsm/p/7079458.html

這里簡(jiǎn)單的解釋一下,select的參數(shù)感凤。第一個(gè)參數(shù)悯周,代表了有多少文件描述符加入了,此時(shí)只有一個(gè)陪竿,第二個(gè)參數(shù)禽翼,把fd每個(gè)參數(shù)對(duì)應(yīng)的標(biāo)志位,一旦這個(gè)標(biāo)志位出現(xiàn)了變動(dòng)族跛,則代表這個(gè)文件描述符出現(xiàn)變化闰挡,socket接入了。其他先可以不管礁哄。

因此在最下面的那一段函數(shù)中长酗,通過(guò)FD_ISSET的方法,判斷變動(dòng)的標(biāo)志位桐绒,找到對(duì)應(yīng)的fd夺脾,把對(duì)應(yīng)的index返回。

這樣就能正確找到哪個(gè)socket茉继。并且處理對(duì)應(yīng)的ZygoteConnection咧叭。

上個(gè)圖總結(jié):


zygote通信原理.png

思考

經(jīng)過(guò)兩者的比較,為什么在4.4.4版本使用select()去做烁竭,而到了7.0版本使用了poll佳簸。為什么這么做?先說(shuō)說(shuō)兩個(gè)函數(shù)之間的區(qū)別颖变。

簡(jiǎn)單的說(shuō)生均,select和poll本質(zhì)上都是對(duì)文件描述符的集合進(jìn)行輪詢(xún)查找,哪些socket出現(xiàn)了變化并且告訴Zygote腥刹。然而api的不同導(dǎo)致兩者之間的策略不一樣马胧。

在4.4時(shí)代,大部分的手機(jī)內(nèi)存吃緊(這一點(diǎn)從runLoop每隔10次就要gc一次就知道了)衔峰,而select的好處就是每一次輪詢(xún)都是直接修正每一個(gè)fd對(duì)應(yīng)的標(biāo)志位佩脊,速度較快蛙粘。缺點(diǎn)是,一段標(biāo)志位使用過(guò)每一個(gè)位上的0或者1來(lái)判斷威彰,也就限制了最大連接數(shù)量出牧。

而7.0時(shí)代,大部分手機(jī)的性能變得比較好了歇盼。資源不再吃緊了舔痕,此時(shí)更換為poll函數(shù)。該函數(shù)的作用和select很相似豹缀。不過(guò)每一次輪詢(xún)fd伯复,都要修改pollfd結(jié)構(gòu)體內(nèi)部的標(biāo)志位。這樣就脫離標(biāo)志位的限制了邢笙。

所以說(shuō)啸如,對(duì)于不同的api的,沒(méi)有最好氮惯,只有最適用叮雳。

愚見(jiàn)

難道沒(méi)辦法,更好的辦法嗎妇汗?有帘不!這只是個(gè)人看法,還記得前幾年流行的ngnx嗎铛纬?這個(gè)的底層是用epoll來(lái)實(shí)現(xiàn)的厌均。

這種實(shí)現(xiàn)和單一的阻塞不一樣唬滑。而是異步的IO告唆。這方法只有Linux 2.6才開(kāi)始支持。這個(gè)方法相比于select和poll晶密。不是簡(jiǎn)單的輪詢(xún)擒悬,因?yàn)楫?dāng)量級(jí)到了一定的時(shí)候,輪詢(xún)的速度必定慢下來(lái)稻艰。而是通過(guò)回調(diào)的機(jī)制去處理。每一次通過(guò)內(nèi)存映射的方式查找對(duì)應(yīng)的fd,并且回調(diào)肢簿。這樣就省去了內(nèi)存在調(diào)用fd時(shí)候造成的拷貝(從內(nèi)核空間到用戶(hù)空間)豆巨。

其次,epoll這個(gè)函數(shù)沒(méi)有數(shù)量的限制元扔,而是由一個(gè)文件描述符去控制所有的文件描述符躯保。

基于這兩個(gè)理由,很明顯epoll才是最佳的選擇澎语。

但是途事,最佳就必須選擇嗎验懊?不,我們只選擇了最合適的尸变。我剛才看了下android 9.0的源碼义图。發(fā)現(xiàn)還是繼續(xù)使用poll機(jī)制。對(duì)于android來(lái)說(shuō)zygote誕生出新的進(jìn)程的情況不多見(jiàn)召烂,量級(jí)遠(yuǎn)沒(méi)有達(dá)到服務(wù)器的地步碱工,加上使用epoll,下面的fork的機(jī)制可能變動(dòng)大骑晶,沒(méi)有選擇也是情理之中痛垛。

當(dāng)然,如果有哥們看過(guò)Handler的源碼桶蛔,就知道Handler有一層ndk層匙头,下層也是用epoll做等待死循環(huán)處理。有機(jī)會(huì)再源碼解析解析仔雷。

總結(jié)

實(shí)際上最后這一段Zygote孵化原理蹂析,我發(fā)現(xiàn)老羅的書(shū),還有網(wǎng)上的資料都說(shuō)不詳細(xì)碟婆,但是這卻是最重要的一環(huán)电抚,是Zygote溝通應(yīng)用程序的核心代碼。特此在此記錄一下竖共。

那么Zygote誕生做了什么蝙叛?在A(yíng)ctivity啟動(dòng)前的角色是什么?現(xiàn)在就明白了公给。

  • 1.Zygote是init進(jìn)程之后第一個(gè)誕生出來(lái)的孵化進(jìn)程借帘。就以Android系統(tǒng)的framework來(lái)說(shuō),Zygote是Android系統(tǒng)一切進(jìn)程的母親淌铐。

  • 2.Zygote第一個(gè)孵化的進(jìn)程是SystemServer進(jìn)程肺然。

  • 3.初始化虛擬機(jī)是通過(guò)jniInvoaction,加載對(duì)應(yīng)的so庫(kù)

  • 4.SystemServer進(jìn)程初始化腿准,AMS际起,WMS,PMS吐葱,DisplayManager(顯示)街望,InputManager(鍵盤(pán)),PowerManager(電源)...

  • 5.Zygote 誕生新的進(jìn)程都是通過(guò)fork誕生的。

  • 6.Zygote 開(kāi)啟socket監(jiān)聽(tīng)死循環(huán)弟跑,在低版本使用select來(lái)阻塞灾前,高版本使用poll來(lái)阻塞。

參考資料:
https://segmentfault.com/a/1190000003063859?utm_source=tag-newest

https://www.cnblogs.com/amanlikethis/p/6915485.html

題外話(huà)

無(wú)語(yǔ)了窖认,沒(méi)辦法發(fā)長(zhǎng)一點(diǎn)的文章豫柬,只能拆開(kāi)來(lái)放出來(lái)了告希。
寫(xiě)的比較粗淺,也不是很專(zhuān)業(yè)烧给⊙嗯迹看到錯(cuò)誤可以找我糾正。估計(jì)很多人都懂這些了础嫡,更多的只是把這兩年學(xué)習(xí)的復(fù)習(xí)和整理指么。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榴鼎,隨后出現(xiàn)的幾起案子伯诬,更是在濱河造成了極大的恐慌,老刑警劉巖巫财,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盗似,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡平项,警方通過(guò)查閱死者的電腦和手機(jī)赫舒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闽瓢,“玉大人接癌,你說(shuō)我怎么就攤上這事】鬯希” “怎么了缺猛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)椭符。 經(jīng)常有香客問(wèn)我荔燎,道長(zhǎng),這世上最難降的妖魔是什么艰山? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任湖雹,我火速辦了婚禮咏闪,結(jié)果婚禮上曙搬,老公的妹妹穿的比我還像新娘。我一直安慰自己鸽嫂,他們只是感情好纵装,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著据某,像睡著了一般橡娄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上癣籽,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天挽唉,我揣著相機(jī)與錄音滤祖,去河邊找鬼。 笑死瓶籽,一個(gè)胖子當(dāng)著我的面吹牛匠童,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塑顺,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼汤求,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了严拒?” 一聲冷哼從身側(cè)響起扬绪,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裤唠,沒(méi)想到半個(gè)月后挤牛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡种蘸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年赊颠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劈彪。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竣蹦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沧奴,到底是詐尸還是另有隱情痘括,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布滔吠,位于F島的核電站纲菌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疮绷。R本人自食惡果不足惜翰舌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冬骚。 院中可真熱鬧椅贱,春花似錦、人聲如沸只冻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喜德。三九已至山橄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舍悯,已是汗流浹背航棱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工睡雇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饮醇。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓入桂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親驳阎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抗愁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • 2018-11-06 這一塊操作系統(tǒng)主要分為兩個(gè)部分,一個(gè)部分是書(shū)本上操作系統(tǒng)的知識(shí)呵晚,還有一部門(mén)是linux的相關(guān)...
    zuoerfeng閱讀 2,222評(píng)論 0 1
  • 本次系列的內(nèi)容如下: Android啟動(dòng)流程——1 序言蜘腌、bootloader引導(dǎo)與Linux啟動(dòng)Android系...
    隔壁老李頭閱讀 6,675評(píng)論 4 24
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復(fù)雜的硬件接口,提供良好的抽象接口饵隙。 管理調(diào)度進(jìn)程撮珠,并將多個(gè)進(jìn)程對(duì)硬件...
    drfung閱讀 3,537評(píng)論 0 5
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個(gè)文件來(lái)操作。那么我們對(duì)與外部設(shè)...
    VD2012閱讀 1,021評(píng)論 0 2
  • “你若不喜歡她怎會(huì)和她做朋友金矛,你若愛(ài)她又怎甘心只和她做朋友芯急。”這是在劉同書(shū)里提到的朋友的理解驶俊,我很同意他的說(shuō)法娶耍。 ...
    Unbelievabledd閱讀 259評(píng)論 0 0