4.2.2 Welcome to Java World

这个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")){

throw new 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){

int fileDesc;

try{

//从环境变量中获取Socket的fd,还记得第3章中介绍的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。不过读者应该提前想到下面两个问题:

谁是客户端?

服务端会怎么处理客户端的消息?

提示 读者应掌握与Socket相关的知识,这些知识对网络编程或简单的IPC使用是会有帮助的。

2.预加载类和资源

现在我们要分析的就是preloadClasses和preloadResources函数了。先来看看preloadClasses。


[—>ZygoteInit.java]

private static void preloadClasses(){

final VMRuntime 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(new InputStreamReader(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 by com.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系统启动慢的原因之一。可对比进行一些优化,但优化是基于对整个系统有比较深入的了解才能实现的。

注意 在本章的拓展思考内容中,我们会讨论Android的启动速度问题。

preloadResources和preloadClass类似,它主要是加载framework-res.apk中的资源。这里就不再介绍它了。

说明 在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;

}


这里出现了一个分水岭,即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来处理。

提示 runSelectLoopMode比较简单,但它使用的select背后所代表的思想却并非那么简单。建议读者以此为契机,认真学习常用的I/O模型,包括阻塞式、非阻塞式、多路复用、异步I/O等,掌握这些知识,对于未来编写大型系统很有帮助。

关于zygote是如何处理请求的,将单独用一节内容进行讨论。