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是如何处理请求的,将单独用一节内容进行讨论。