4.2.2 Welcome to Java World
最后更新于:2022-04-02 05:49:01
这个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 fds = new ArrayList();
ArrayList 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是如何处理请求的,将单独用一节内容进行讨论。
';