詳解Android應用main函數(shù)的調(diào)用
啟動App進程
Activity啟動過程的一環(huán)是調(diào)用ActivityStackSupervisor.startSpecificActivityLocked,如果App所在進程還不存在,首先調(diào)用AMS的startProcessLocked:
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
r.task.stack.setLaunchTime(r);
if (app != null && app.thread != null) {
//...
}
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false, true);
}
startProcessLocked函數(shù)有多個重載,看最長的那個,最重要是調(diào)用了Process.start。
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);
第一個參數(shù)是android.app.ActivityThread,執(zhí)行的目標,后文用到再說。
Process.start簡單地調(diào)用了startViaZygote,封裝一些參數(shù),再調(diào)用zygoteSendArgsAndGetResult。顧名思義,接下來進程的啟動工作交給Zygote。
Zygote
Zygote翻譯過來意思是“受精卵”,這也是Zygote的主要工作——孵化進程。概括Zygote的主要工作有以下三點,ZygoteInit的main函數(shù)也清晰地體現(xiàn)了。Zygote的啟動和其他作用另文分析,這次關(guān)注Zygote對Socket的監(jiān)聽。
1.registerZygoteSocket:啟動Socket的Server端
2.preload:預加載資源
3.startSystemServer:啟動system_server進程
public static void main(String argv[]) {
// Mark zygote start. This ensures that thread creation will throw
// an error.
ZygoteHooks.startZygoteNoThreadCreation();
try {
//...
registerZygoteSocket(socketName);
//...
preload();
//...
if (startSystemServer) {
startSystemServer(abiList, socketName);
}
//...
runSelectLoop(abiList);
//...
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (Throwable ex) {
//...
}
}
最后Zygote執(zhí)行runSelectLoop,無限循環(huán)等待處理進程啟動的請求。
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);
}
}
}
}
}
與Zygote通信使用的IPC方式是socket,類型是LocalSocket,實質(zhì)是對Linux的LocalSocket的封裝。與記憶中socket綁定需要IP和端口不同,LocalSocket使用FileDescriptor文件描述符,它可以表示文件,也可以表示socket。
runSelectLoop維護了兩個列表,分別保存文件描述符FileDescriptor和ZygoteConnection,兩者一一對應。index=0的FileDescriptor表示ServerSocket,因此index=0的ZygoteConnection用null填充。
在每次循環(huán)中,判斷fds里哪個可讀:
- 當i=0時,表示有新的client,調(diào)用acceptCommandPeer創(chuàng)建ZygoteConnection并保存
- 當i>0時,表示已建立連接的socket中有新的命令,調(diào)用runOnce函數(shù)執(zhí)行
fork進程
runOnce函數(shù)非常長,我們只關(guān)注進程創(chuàng)建部分。
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
//...
try {
//...
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) {
//...
}
try {
if (pid == 0) {
// in child
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
// should never get here, the child is expected to either
// throw ZygoteInit.MethodAndArgsCaller or exec().
return true;
} else {
// in parent...pid of < 0 means failure
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
} finally {
//...
}
}
首先調(diào)用了forkAndSpecialize函數(shù),創(chuàng)建進程返回一個pid。
public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
String instructionSet, String appDataDir) {
VM_HOOKS.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true);
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
}
VM_HOOKS.postForkCommon();
return pid;
}
在forkAndSpecialize中核心就是利用JNI調(diào)用native的fork函數(shù),調(diào)用之前會執(zhí)行VM_HOOKS.preFork(),調(diào)用之后執(zhí)行VM_HOOKS.postForkCommon()。
VM_HOOKS.preFork()的功能是停止Zygote的4個Daemon子線程的運行,確保Zygote是單線程,提升fork效率。當線程停止之后初始化gc堆。VM_HOOKS.postForkCommon()可以看作是逆操作,關(guān)于虛擬機更加深入的內(nèi)容暫不討論。
native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
nativeForkSystemServer是一個native函數(shù),對應com_android_internal_os_Zygote.cpp的com_android_internal_os_Zygote_nativeForkAndSpecialize,繼續(xù)調(diào)用了ForkAndSpecializeCommon,最核心一句則是調(diào)用fork函數(shù)。
pid_t pid = fork();
簡單回憶fork函數(shù)作用,它復制當前進程,屬性和當前進程相同,使用copy on write(寫時復制)。執(zhí)行函數(shù)后,新進程已經(jīng)創(chuàng)建,返回的pid=0;對于被復制的進程,返回新進程的pid;出現(xiàn)錯誤時,返回-1。
因此,執(zhí)行forkAndSpecialize函數(shù)后,runOnce后續(xù)的代碼分別在兩個進程中執(zhí)行,判斷當前的pid,區(qū)分是在當前進程還是新進程。
- pid == 0:新進程,調(diào)用handleChildProc
- pid != 0:當前進程,調(diào)用handleParentProc
handleParentProc函數(shù)是當前進程進行清理的過程,比較簡單。我們重點來看新進程開展工作的handleChildProc函數(shù)。
新進程的初始化
private void handleChildProc(Arguments parsedArgs,
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
//...
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 */);
}
}
兩種啟動方式:
- WrapperInit.execApplication
- RuntimeInit.zygoteInit
第一種的目的不太懂,先掛起,后續(xù)分析。
第二種RuntimeInit.zygoteInit,繼續(xù)調(diào)用applicationInit,離我們的目標越來越近了,最終調(diào)用到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 {
m = cl.getMethod("main", new Class[] { String[].class });
} catch (NoSuchMethodException ex) {
//...
}
int modifiers = m.getModifiers();
if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
throw new RuntimeException(
"Main method is not public and static on " + className);
}
throw new ZygoteInit.MethodAndArgsCaller(m, argv);
}
在這里使用反射獲得需要被執(zhí)行的類和函數(shù),目標函數(shù)當然就是main,目標類來自ActivityManagerService.startProcessLocked里的變量entryPoint,前面有說過。
entryPoint = "android.app.ActivityThread"
最后一句執(zhí)行throw new ZygoteInit.MethodAndArgsCaller(m, argv),讓人有些費解。
public static class MethodAndArgsCaller extends Exception
implements Runnable {
//...
public void run() {
try {
mMethod.invoke(null, new Object[] { mArgs });
} catch (IllegalAccessException ex) {
//...
}
}
}
通過上面的分析,容易知道MethodAndArgsCaller就是App的主線程,里面的run方法實現(xiàn)了反射的調(diào)用。什么時候觸發(fā)執(zhí)行,為什么要這樣設計?
既然MethodAndArgsCaller是異常,拋出它肯定某個地方會接收,回顧一路的調(diào)用鏈:
- ZytoteInit.main
- ZytoteInit.runSelectLoop
- ZygoteConnection.runOnce
- ZygoteConnection.handleChildProc
- RuntimeInit.zygoteInit
- RuntimeInit.applicationInit
- RuntimeInit.invokeStaticMain
看前面的ZytoteInit.main函數(shù),catch了MethodAndArgsCaller異常,直接調(diào)用了run函數(shù)。注釋里解釋了為什么要這樣做:
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.
函數(shù)在虛擬機是保存在棧中,每調(diào)用一個函數(shù),就將函數(shù)相關(guān)數(shù)據(jù)壓入棧;執(zhí)行完函數(shù),將函數(shù)從棧中彈出。因此,棧底的就是main函數(shù)。
在上面的研究中,新進程創(chuàng)建后,經(jīng)歷一系列函數(shù)的調(diào)用才到main函數(shù),如果直接調(diào)用main函數(shù),調(diào)用鏈中關(guān)于初始化的函數(shù)會一直存在。為了清理這部分函數(shù),使用了拋出異常的方式,沒有捕獲異常的函數(shù)會馬上結(jié)束,ZytoteInit.main之上的函數(shù)都會結(jié)束,達到清理的目的。
最后補充一點,從handleChildProc函數(shù)開始,一系列過程調(diào)用了ActivityThread的main函數(shù),這不是啟動App獨有的,后續(xù)研究啟動SystemServer進程時,你會發(fā)現(xiàn)邏輯都是一樣。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Parcleable接口的調(diào)用源碼層分析
這篇文章主要給大家介紹了關(guān)于利用Kotlin如何實現(xiàn)Android開發(fā)中的Parcelable的相關(guān)資料,并且給大家介紹了關(guān)于Android Parcleable源碼層問題,需要的朋友可以參考下2022-12-12
Intellij IDEA + Android SDK + Genymotion Emulator打造最佳Android
本文介紹Lorinnn在開發(fā)Android過程不斷跌打滾爬中安裝的一套開發(fā)環(huán)境,相信你在使用后同樣有不錯的體會。2014-07-07
Android RecyclerView自定義上拉和下拉刷新效果
這篇文章主要為大家詳細介紹了Android RecyclerView自定義上拉和下拉刷新效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02
Android 應用適配 Android 7.0 權(quán)限要求詳解
今天小編就為大家分享一篇Android 應用適配 Android 7.0 權(quán)限要求詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Android?Banner本地和網(wǎng)絡輪播圖使用介紹
大家好,本篇文章講的是Android?Banner本地和網(wǎng)絡輪播圖使用介紹,感興趣的同學趕快來看一看吧,希望本篇文章對你起到幫助2021-11-11

