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)系。
實(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ì)列中。
- 把fds設(shè)置到poll進(jìn)行監(jiān)聽(tīng)
- 更新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è)部分:
- 從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é):
思考
經(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í)和整理指么。