您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關怎么在Android中調用main函數,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
啟動App進程
Activity啟動過程的一環是調用ActivityStackSupervisor.startSpecificActivityLocked,如果App所在進程還不存在,首先調用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函數有多個重載,看最長的那個,最重要是調用了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);
第一個參數是android.app.ActivityThread,執行的目標,后文用到再說。
Process.start簡單地調用了startViaZygote,封裝一些參數,再調用zygoteSendArgsAndGetResult。顧名思義,接下來進程的啟動工作交給Zygote。
Zygote
Zygote翻譯過來意思是“受精卵”,這也是Zygote的主要工作——孵化進程。概括Zygote的主要工作有以下三點,ZygoteInit的main函數也清晰地體現了。Zygote的啟動和其他作用另文分析,這次關注Zygote對Socket的監聽。
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執行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); } } } } }
與Zygote通信使用的IPC方式是socket,類型是LocalSocket,實質是對Linux的LocalSocket的封裝。與記憶中socket綁定需要IP和端口不同,LocalSocket使用FileDescriptor文件描述符,它可以表示文件,也可以表示socket。
runSelectLoop維護了兩個列表,分別保存文件描述符FileDescriptor和ZygoteConnection,兩者一一對應。index=0的FileDescriptor表示ServerSocket,因此index=0的ZygoteConnection用null填充。
在每次循環中,判斷fds里哪個可讀:
當i=0時,表示有新的client,調用acceptCommandPeer創建ZygoteConnection并保存
當i>0時,表示已建立連接的socket中有新的命令,調用runOnce函數執行
fork進程
runOnce函數非常長,我們只關注進程創建部分。
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 { //... } }
首先調用了forkAndSpecialize函數,創建進程返回一個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調用native的fork函數,調用之前會執行VM_HOOKS.preFork(),調用之后執行VM_HOOKS.postForkCommon()。
VM_HOOKS.preFork()的功能是停止Zygote的4個Daemon子線程的運行,確保Zygote是單線程,提升fork效率。當線程停止之后初始化gc堆。VM_HOOKS.postForkCommon()可以看作是逆操作,關于虛擬機更加深入的內容暫不討論。
native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
nativeForkSystemServer是一個native函數,對應com_android_internal_os_Zygote.cpp的com_android_internal_os_Zygote_nativeForkAndSpecialize,繼續調用了ForkAndSpecializeCommon,最核心一句則是調用fork函數。
pid_t pid = fork();
簡單回憶fork函數作用,它復制當前進程,屬性和當前進程相同,使用copy on write(寫時復制)。執行函數后,新進程已經創建,返回的pid=0;對于被復制的進程,返回新進程的pid;出現錯誤時,返回-1。
因此,執行forkAndSpecialize函數后,runOnce后續的代碼分別在兩個進程中執行,判斷當前的pid,區分是在當前進程還是新進程。
pid == 0:新進程,調用handleChildProc
pid != 0:當前進程,調用handleParentProc
handleParentProc函數是當前進程進行清理的過程,比較簡單。我們重點來看新進程開展工作的handleChildProc函數。
新進程的初始化
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
第一種的目的不太懂,先掛起,后續分析。
第二種RuntimeInit.zygoteInit,繼續調用applicationInit,離我們的目標越來越近了,最終調用到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); }
在這里使用反射獲得需要被執行的類和函數,目標函數當然就是main,目標類來自ActivityManagerService.startProcessLocked里的變量entryPoint,前面有說過。
entryPoint = "android.app.ActivityThread"
最后一句執行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方法實現了反射的調用。什么時候觸發執行,為什么要這樣設計?
既然MethodAndArgsCaller是異常,拋出它肯定某個地方會接收,回顧一路的調用鏈:
ZytoteInit.main
ZytoteInit.runSelectLoop
ZygoteConnection.runOnce
ZygoteConnection.handleChildProc
RuntimeInit.zygoteInit
RuntimeInit.applicationInit
RuntimeInit.invokeStaticMain
看前面的ZytoteInit.main函數,catch了MethodAndArgsCaller異常,直接調用了run函數。注釋里解釋了為什么要這樣做:
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.
函數在虛擬機是保存在棧中,每調用一個函數,就將函數相關數據壓入棧;執行完函數,將函數從棧中彈出。因此,棧底的就是main函數。
在上面的研究中,新進程創建后,經歷一系列函數的調用才到main函數,如果直接調用main函數,調用鏈中關于初始化的函數會一直存在。為了清理這部分函數,使用了拋出異常的方式,沒有捕獲異常的函數會馬上結束,ZytoteInit.main之上的函數都會結束,達到清理的目的。
最后補充一點,從handleChildProc函數開始,一系列過程調用了ActivityThread的main函數,這不是啟動App獨有的,后續研究啟動SystemServer進程時,你會發現邏輯都是一樣。
上述就是小編為大家分享的怎么在Android中調用main函數了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。