企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
这个Java世界的入口在哪里?根据前面的分析,CallStaticVoidMethod最终将调用com.android.internal.os.ZygoteInit的main函数,下面就来看看这个入口函数。代码如下所示: **ZygoteInit.java** ~~~ public static void main(String argv[]) { try { SamplingProfilerIntegration.start(); //①注册Zygote用的socket registerZygoteSocket(); //②预加载类和资源 preloadClasses(); preloadResources(); ...... // 强制一次垃圾收集 gc(); //我们传入的参数满足if分支 if (argv[1].equals("true")) { startSystemServer();//③启动system_server进程 }else if (!argv[1].equals("false")) { thrownew RuntimeException(argv[0] + USAGE_STRING); } // ZYGOTE_FORK_MODE被定义为false,所以满足else的条件 if(ZYGOTE_FORK_MODE) { runForkMode(); }else { runSelectLoopMode();//④zygote调用这个函数 } closeServerSocket();//关闭socket }catch (MethodAndArgsCaller caller) { caller.run();//⑤很重要的caller run函数,以后分析 }catch (RuntimeException ex) { closeServerSocket(); throw ex; } ...... } ~~~ 在ZygoteInit的main函数中,我们列举出了5大关键点,下面对其一一进行分析。先看第一点:registerZygoteSocket。 1. 建立IPC通信服务端——registerZygoteSocket Zygote以及系统中其他程序的通信没有使用Binder,而是采用了基于AF_UNIX类型的Socket。registerZygoteSocket函数的使命正是建立这个Socket。代码如下所示: **ZygoteInit.java** ~~~ private static void registerZygoteSocket() { if(sServerSocket == null) { intfileDesc; try{ //从环境变量中获取Socket的fd,还记得第3章init中介绍的zygote是如何启动的吗? //这个环境变量由execv传入。 String env = System.getenv(ANDROID_SOCKET_ENV); fileDesc = Integer.parseInt(env); } try{ //创建服务端Socket,这个Socket将listen并accept Client sServerSocket= new LocalServerSocket(createFileDescriptor(fileDesc)); } } } ~~~ registerZygoteSocket很简单,就是创建一个服务端的Socket。不过读者应该提前想到下面两个问题: - 谁是客户端? - 服务端会怎么处理客户端的消息? >[info]建议:读者要好好学习与Socket相关的知识,这些知识对网络编程或简单的IPC使用,是会有帮助的。 2. 预加载类和资源 现在我们要分析的就是preloadClasses和preloadResources函数了。先来看看preloadClasses。 **ZygoteInit.java** ~~~ private static void preloadClasses() { finalVMRuntime runtime = VMRuntime.getRuntime(); //预加载类的信息存储在PRELOADED_CLASSES变量中,它的值为"preloaded-classes" InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream( PRELOADED_CLASSES); if(is == null) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES +"."); }else { ...... //做一些统计和准备工作 try { BufferedReader br = new BufferedReader(newInputStreamReader(is), 256); //读取文件的每一行,忽略#开头的注释行 int count = 0; String line; String missingClasses = null; while ((line = br.readLine()) != null) { line = line.trim(); if(line.startsWith("#") || line.equals("")) { continue; } try { //通过Java反射来加载类,line中存储的是预加载的类名 Class.forName(line); ...... count++; } catch(ClassNotFoundException e) { ...... } catch (Throwable t) { ...... } } ...... //扫尾工作 } } ~~~ preloadClasses看起来是如此简单,但是你知道它有多少个类需要预先加载吗? 用coolfind在framework中搜索名为“preloaded-classes”的文件,最后会在framework/base目录下找到。它是一个文本文件,内容如下: ~~~ # Classes which are preloaded bycom.android.internal.os.ZygoteInit. # Automatically generated by # frameworks/base/tools/preload/WritePreloadedClassFile.java. # MIN_LOAD_TIME_MICROS=1250 //超时控制 android.R$styleable android.accounts.AccountManager android.accounts.AccountManager$4 android.accounts.AccountManager$6 android.accounts.AccountManager$AmsTask android.accounts.AccountManager$BaseFutureTask android.accounts.AccountManager$Future2Task android.accounts.AuthenticatorDescription android.accounts.IAccountAuthenticatorResponse$Stub android.accounts.IAccountManager$Stub android.accounts.IAccountManagerResponse$Stub ......//一共有1268行 ~~~ 这个preload-class一共有1268行,试想,加载这么多类得花多少时间! * * * * * **说明**:preload_class文件由framework/base/tools/preload工具生成,它需要判断每个类加载的时间是否大于1250微秒,超过这个时间的类就会被写到preload-classes文件中,最后由zygote预加载。这方面的内容,读者可参考有关preload工具中的说明,这里就不再赘述。 * * * * * preloadClass函数的执行时间比较长,这是导致Android系统启动慢的原因之一。对这一块可以做一些优化,但优化是基于对整个系统有比较深入了解才能实现的。 >[info]**注意**:在拓展思考部分中,我们会讨论Android启动速度问题。 preloadResources和preloadClass类似,它主要是加载framework-res.apk中的资源。这里就不再介绍它了。 >[info] **说明**:在UI编程中常使用的com.android.R.XXX资源,是系统默认的资源,它们就是由Zygote加载的。 3. 启动system_server 我们现在要分析的是第三个关键点:startSystemServer。这个函数会创建Java世界中系统Service所驻留的进程system_server,该进程是framework的核心。如果它死了,就会导致zygote自杀。先来看看这个核心进程是如何启动的。 **ZygoteInit.java** ~~~ private static boolean startSystemServer() throws MethodAndArgsCaller, RuntimeException { //设置参数 String args[] = { "--setuid=1000",//uid和gid等设置 "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010, 3001,3002,3003", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", //进程名,叫system_server "com.android.server.SystemServer", //启动的类名 }; ZygoteConnection.Arguments parsedArgs = null; int pid; try { //把上面字符串数组参数转换成Arguments对象。具体内容请读者自行分析。 parsedArgs = new ZygoteConnection.Arguments(args); int debugFlags = parsedArgs.debugFlags; //fork一个子进程,看来,这个子进程就是system_server进程。 pid = Zygote.forkSystemServer( parsedArgs.uid,parsedArgs.gid, parsedArgs.gids,debugFlags, null); }catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* 关于fork的知识,请读者务花些时间去研究。如果对fork具体实现还感兴趣,可参考 《Linux内核源代码情景分析》一书。(该书由浙江大学出版社出版,作者为毛德操、胡希明) 下面代码中,如果pid为零,则表示处于子进程中,也就是处于system_server进程中。 */ if(pid == 0) { //① system_server进程的工作 handleSystemServerProcess(parsedArgs); } //zygote返回true return true; } ~~~ OK,这里出现了一个分水岭,即Zygote进行了一次无性繁殖,分裂出了一个system_server进程(即代码中的Zygote.forkSystemServer这句话)。关于它的故事,我们会在后文做专门分析,这里先说Zygote。 4. 有求必应之等待请求——runSelectLoopMode 当Zygote从startSystemServer返回后,将进入第四个关键函数:runSelectLoopMode。前面,在第一个关键点registerZygoteSocket中注册了一个用于IPC的Socket,不过那时还没有地方用到它。它的用途将在这个runSelectLoopMode中体现出来,请看下面的代码: **ZygoteInit.java** ~~~ private static void runSelectLoopMode() throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList(); ArrayList<ZygoteConnection> peers = new ArrayList(); FileDescriptor[] fdArray = new FileDescriptor[4]; //sServerSocket是我们先前在registerZygoteSocket建立的Socket fds.add(sServerSocket.getFileDescriptor()); peers.add(null); int loopCount = GC_LOOP_COUNT; while (true) { int index; try { fdArray = fds.toArray(fdArray); /* selectReadable内部调用select,使用多路复用I/O模型。 当有客户端连接或有数据时,则selectReadable就会返回。 */ index = selectReadable(fdArray); } else if (index == 0) { //如有一个客户端连接上,请注意客户端在Zygote的代表是ZygoteConnection ZygoteConnection newPeer = acceptCommandPeer(); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { boolean done; //客户端发送了请求,peers.get返回的是ZygoteConnection //后续处理将交给ZygoteConnection的runOnce函数完成。 done = peers.get(index).runOnce(); } } ~~~ runSelectLoopMode比较简单,就是: - 处理客户连接和客户请求。其中客户在Zygote中用ZygoteConnection对象来表示。 - 客户的请求由ZygoteConnection的runOnce来处理。 >[info]**提示**:runSelectLoopMode比较简单,但它使用的select的背后所代表的思想却并非简单。建议读者以此为契机,认真学习常用的I/O模型,包括阻塞式、非阻塞式、多路复用、异步I/O等,掌握这些知识,对于未来编写大型系统很有帮助。 关于Zygote是如何处理请求的,将单独用一节内容进行讨论。