Zygote是Android中非常重要的一个进程,它和Init进程、SystemServer进程在Android中有着不可替代的地位。
Zygote简介
Linux的进程是通过系统调用fork产生的,fork出的子进程除了内核中的一些核心的数据结构和父进程不相同外,其余的内存映像都是和父进程共享的。只有当子进程需要去改写这些共享的内存时,操作系统才会为子进程分配一个新页面。这就是所谓的写时复制(Copy On Write)
通常子进程被fork出后,会继续执行系统调用exec。exec将用一个新的可执行文件的内容替换当前进程的代码段、数据段、堆和栈。
fork加exec是Linux启动应用的标准做法,Init进程也是这样来启动各种服务的。
不过
Zygote创建应用程序时却只是用了fork,没有调用exec
首先,Android应用中跑的的是虚拟机,虚拟机中Java代码的不同才造成了应用的区别,而对于基础的运行环境,要求却是一样的。
其次,Zygote在初始化时就会会创建虚拟机,同时把需要的系统类库和资源文件加载的内存中。而Zygote在fork出子进程后,这个子进程也会得到一个已经加载好基础资源的虚拟机。
这样,接下来只需要装载APK相关的资源就可以运行了,可以做到提升效率
Zygote进程的初始化
Zygote进程在Init进程中以service的方式启动的。我们来看下它在init.rc中的配置内容:
import /init.${ro.zygote}.rc
on late-init
......
# Now we can start zygote for devices with file based encryption
# 这里触发启动 zygote
trigger zygote-start
on zygote-start && (*) //*用来省略一些属性值的判断
start netd
start zygote
start zygote_secondary
从Android 5.0开始,Android开始支持64位编译,Zygote本身也会有32位和64位的区别,因此,通过ro.zygote属性来控制启动不同版本的Zygote进程。在9.0的源码中,存在4个相关文件:
init.zygote32.rc
init.zygote32_64.rc
init.zygote64.rc
init.zygote64_32.rc
挑个特殊的看下(init.zygote32_64.rc):
service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
......
service zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
class main
......
- 从文件内容中可以看到定义了两个
Zygote服务:zygote和zygote_secondary - 两个服务最大的区别是
可执行文件不同:一个是app_process32、一个是app_process64 init.zygote64_32.rc文件就不贴出来了,其实就是把两个可执行文件交换一下- 对于
init.zygote32.rc和init.zygote64.rc分别只有一个Zygote服务
从这里我们就可以知道,Android将会支持4中运行模式:
- 纯32位模式:
ro.zygote的值为zygote32 - 32位模式为主,64位为辅:
ro.zygote的值为zygote32_64 - 纯64位模式:
ro.zygote的值为zygote64 - 64位模式为主,32位为辅:
ro.zygote的值为zygote64_32
而对于可执行文件app_process,源文件路径在frameworks/base/cmds/app_process,不过在学习源码前,我们先来看下app_process用法
app_process的用法
上面我们知道Zygote的启动是通过app_process来实现的
app_process主要作用是解析启动参数,然后根据启动参数选择不同的启动模式
为了更好的理解app_process,我们先来看明白它是怎么使用的。不过关于app_process参数的使用说明,源码中却只有:
fprintf(stderr,
"Usage: app_process [java-options] cmd-dir start-class-name [options]\n");
有些太简洁了哈。。。。
我们还是来看几个示例吧,以Zygote为例:
/system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
再来一个有趣版本(下面这段指令的目的是:运行一个Application,包含main函数类的名称为Test):
app_process -Djava.class.path=/data/local/tmp/Test.dex /data/lee Test
结合源码中的Usage(纯净版)...和这两个例子,我们可以这么总结:
-Xzygote和-Djava.class.p**:属于[java-options],这些参数会传给虚拟机,并且参数必须以-开头,一旦遇到非-或者--,表示[java-options]结束/system/bin和/data/lee:属于cmd-dir,程序的运行目录,随便指定即可,文件操作时会以此为当前路径,正经的大多运行在/system/bin下Test:属于start-class-name,声明入口类的名称,有包名的话需要加上包名--zygote:属于[options],这些参数都以符号--开头。参数--zygote表示要启动Zygote进程
app_process的main()
有了上面app_process用法的铺垫,我们再来分析main函数就会更容易些,老样子,先看整体:
int main(int argc, char* const argv[])
{
// 1. 创建 AppRuntime 对象
// 2. 保存 Java-Option 参数
// 3. 解析 Option 参数:--开头的那种
// 4. 根据参数的解析结果,准备ZygoteInit或者RuntimeInit相关的参数
// 5. 将本进程名称改为nice-name指定的字符串
// 6. 根据参数的解析结果,启动对应的Java类
}
很清晰了然了哈,我们再看下具体的源码
创建AppRuntime对象
main()源码:
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
AppRuntime是在app_process中定义的类,继承了系统的AndroidRuntime类。AndroidRuntime类是底层很重要很重要的一个类,主要作用是创建和初始化虚拟机,等下详细分析- 整个
main()的后续流程都是通过这个runtime来操作的
保存Java-Option参数
......
for (i = 0; i < argc; i++) {
// 省略know_command相关
......
if (argv[i][0] != '-') {
//如果不是-开头的话,解析结束
//按照app_process的参数规则:java-option后面是cmd-dir,不是以-开头的
//所以,正常情况解析到java-option就会停止
break;
}
if (argv[i][1] == '-' && argv[i][2] == 0) {
//如果是--开头并且后面是空字符时,结束解析
//不知道加这个的目的是干啥
++i; // Skip --.
break;
}
runtime.addOption(strdup(argv[i]));
// 省略打印信息
......
}
......
解析Option参数
Option参数在源码中称为runtime argument
//需要解析出来的参数都在这里了
bool zygote = false;
bool startSystemServer = false;
bool application = false;
String8 niceName;
String8 className;
// 按照参数的规则,上面java-option解析完后,紧接着是cmd-dir,也就是parent dir
// 对于cmd dir不需要做额外处理,直接跳过
++i; // Skip unused "parent dir" argument.
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
//如果不是--开头,就认为是class name
className.setTo(arg);
break;
} else {
--i;
break;
}
}
准备ZygoteInit或者RuntimeInit需要的参数
//定义一个字符参数集合
Vector<String8> args;
if (!className.isEmpty()) {
// We're not in zygote mode, the only argument we need to pass
// to RuntimeInit is the application argument.
// 非Zygote模式,没有删掉官方注释,开心不
// The Remainder of args get passed to startup class main(). Make
// copies of them before we overwrite them with the process name.
args.add(application ? String8("application") : String8("tool"));
runtime.setClassNameAndArgs(className, argc - i, argv + i);
// 省略打印部分
......
} else {
// We're in zygote mode.
maybeCreateDalvikCache();
if (startSystemServer) {
args.add(String8("start-system-server"));
}
// 省略ABI属性值的获取
......
String8 abiFlag("--abi-list=");
abiFlag.append(prop);
args.add(abiFlag);
// In zygote mode, pass all remaining arguments to the zygote
// main() method.
// Zygote模式,app_process的参数会被统一打包进参数集合中
for (; i < argc; ++i) {
args.add(String8(argv[i]));
}
}
将进程名称改为nice-name指定的字符串
if (!niceName.isEmpty()) {
// 这是AndroidRuntime提供的函数,设置进程名称
runtime.setArgv0(niceName.string(), true /* setProcName */);
}
// 在AndroidRuntime.cpp中
void AndroidRuntime::setArgv0(const char* argv0, bool setProcName) {
if (setProcName) {
int len = strlen(argv0);
if (len < 15) {
pthread_setname_np(pthread_self(), argv0);
} else {
pthread_setname_np(pthread_self(), argv0 + len - 15);
}
}
memset(mArgBlockStart, 0, mArgBlockLength);
strlcpy(mArgBlockStart, argv0, mArgBlockLength);
}
启动对应的Java类
if (zygote) {
// 如果是--zygote,则执行ZygoteInit类,启动Zygote进程
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
// 如果是指定了class name,则通过RuntimeInit执行传进来的类
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
app_process在Android中的应用
通过上面的源码分析我们知道:app_process除了能启动Zygote进程,也可以使用它来执行某个系统的Java类
Android中的常用工具am就是一个很好的例子:
am是一个发送Intent的工具,像am start、am broadcast等。
但是,am实际上只是一个包含几行代码的脚本文件,它的功能都是通过调用app_process来完成的
#!/system/bin/sh
if [ "$1" != "instrument" ] ; then
cmd activity "$@"
else
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
fi
有木有,有木有,Android还是蛮惊喜的,哈哈哈!
大家可以仔细阅读下Am.java这个类,很有特点,调用关系找起来是真滴费神,加油哟!
启动虚拟机-AndroidRuntime类
在分析app_process的main()时我们知道AppRuntime继承的AndroidRuntime类。
AndroidRuntime类是一个很重要的类,它负责启动虚拟机以及Java线程AndroidRuntime类在一个进程中只有一个实例对象,保存在全局变量gCurRuntime中
构造函数
AndroidRuntime类构造函数如下:
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :
mExitWithoutCleanup(false),
mArgBlockStart(argBlockStart),
mArgBlockLength(argBlockLength)
{
// 初始化skia图形系统
SkGraphics::Init();
// Pre-allocate enough space to hold a fair number of options.
mOptions.setCapacity(20);
// 只能被初始化一次
assert(gCurRuntime == NULL); // one per process
gCurRuntime = this;
}
启动虚拟机
本章只是简单介绍下虚拟机的启动过程,具体的实现细节后面单独拉出一篇来学习
在app_process的main()函数中,最后调用了runtime.start()来执行Java类,这个start()函数就是在AndroidRuntime中定义的。代码比较长,我们还是先看整体流程:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
// 1. 打印启动log
// 2. 获取系统目录
// 3. 启动虚拟机
// 4. 调用onVmCreated函数
// 5. 注册系统的JNI函数
// 6. 准备Java类的main函数的相关参数
// 7. 调用ZygoteInit类的main方法
}
打印启动Log
源码片段:
ALOGD(">>>>>> START %s uid %d <<<<<<\n",
className != NULL ? className : "(unknown)", getuid());
我们再看下系统启动过程的Log片段:
00:00:21.226808 3244 3244 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<<
- 这段
Log标志着Android系统的启动 - 因为以后的应用进程都是从
Zygote进程fork出来的,所以后面不会再执行start()函数了 - 如果
Android的系统log中反复出现这段内容,而输出ID都是Zygote,则说明系统可能出现问题,Zygote进程在不断地重启
获取系统目录
源码片段
const char* rootDir = getenv("ANDROID_ROOT");
if (rootDir == NULL) {
rootDir = "/system";
if (!hasDir("/system")) {
LOG_FATAL("No root directory specified, and /android does not exist.");
return;
}
setenv("ANDROID_ROOT", rootDir, 1);
}
- 系统目录从环境变量
ANDROID_ROOT读取 - 如果没有设置,则默认设置为
/system- 系统目录是在
Init进程中创建出来
- 系统目录是在
- 如果
/system目录不存在,直接退出
启动虚拟机
源码片段
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
通过startVm启动虚拟机,我们先往下看,后面会详细介绍。
调用onVmCreated函数
onVmCreated(env);
onVmCreated函数是一个虚函数,对于app_process来说实际上调用的是AppRuntime的重载函数,下面是AppRuntime中的代码逻辑:
virtual void onVmCreated(JNIEnv* env)
{
if (mClassName.isEmpty()) {
return; // Zygote. Nothing to do here.
}
// 省略一部分和类加载有关的很有意思的注释
......
char* slashClassName = toSlashClassName(mClassName.string());
mClass = env->FindClass(slashClassName);
if (mClass == NULL) {
ALOGE("ERROR: could not find class '%s'\n", mClassName.string());
}
free(slashClassName);
mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
}
在AppRuntime的onVmCreated函数中:
- 如果是
Zygote进程,变量mClassName的值会为null,会立刻返回 - 如果是一个普通
Java类的调用,mClassName中会存放类的名称 toSlashClassName(mClassName.string())的作用是将类名转换为类的全限定名类名像com.android.Zygote转化为全限定名就变为/com/android/Zygote
注册系统的JNI函数
源码片段
/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
startReg通过调用register_jni_procs函数将全局数组gRegJNI中的JNI本地函数注册到虚拟机中:
/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
......
// 这个函数会修改当前进程的全局变量gCreateThreadFn函数指针为 javaCreateThreadEtc()
// 此之后那么新建的子线程便会
// 先执行javaCreateThreadEtc() -> javaThreadShell() -> javaAttachThread() -> jvm->AttachCurrentThread()
// 此时从普通C层线程成为JVM线程
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
......
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
......
}
而对于gRegJNI数组,格式为
static const RegJNIRec gRegJNI[] = {
......
REG_JNI(register_android_util_Log),
......
REG_JNI(register_android_os_Binder),
......
REG_JNI(register_android_graphics_Paint),
......
REG_JNI(register_android_app_Activity),
......
};
省略了很多元素,该数组中每一个成员都代表一类文件的JNI映射,其中REG_JNI是一个宏定义,该宏的作用就是调用相应的JNI注册方法
准备Java类的main函数的相关参数
源码片段
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
上面是非常经典的在Native层创建Java层对象的操作:
- 创建一个
java.lang.String的数组对象 - 并根据传入的参数对数组对象逐个元素进行赋值
调用ZygoteInit类的main方法
源码片段
// 转化为全限定名
char* slashClassName = toSlashClassName(className != NULL ? className : "");
// 获取类的class对象
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
// 执行main函数
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
// 此处main函数执行完毕,执行一些结束操作
free(slashClassName);
ALOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
ALOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)
ALOGW("Warning: VM did not shut down cleanly\n");
调用main方法前:
- 先通过
GetStaticMethodID获得main方法的ID - 然后使用
CallStaticVoidMethod来调用Java层的函数
到这里,Zygote进程的初始化过程将转到Java层了,还记得app_process的main()函数中的启动逻辑么?
回顾一下哈:
if (zygote) {
// 如果是--zygote,则执行ZygoteInit类,启动Zygote进程
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
// 如果是指定了class name,则通过RuntimeInit执行传进来的类
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
}
接下来,我们就来看看ZygoteInit.java类吧
初始化工作-ZygoteInit类
ZygoteInit类负责Zygote进程Java层的初始化工作。
入口方法main()的具体代码如下(注释比较详细):
public static void main(String argv[]) {
// 创建Zygote服务管理类,用来注册socket监听
ZygoteServer zygoteServer = new ZygoteServer();
// Mark zygote start. This ensures that thread creation will throw
// an error.
// 调用此方法后,虚拟机会拒绝线程的创建,如果此时创建会产生错误
ZygoteHooks.startZygoteNoThreadCreation();
// Zygote goes into its own process group.
Os.setpgid(0, 0);
//......
final Runnable caller;
try {
......省略打印相关
// 启动DDMS虚拟机监控调试服务
RuntimeInit.enableDdms();
// 参数解析
boolean startSystemServer = false;
String socketName = "zygote";
String abiList = null;
boolean enableLazyPreload = false;
for (int i = 1; i < argv.length; i++) {
if ("start-system-server".equals(argv[i])) {
// 开启系统服务
startSystemServer = true;
} else if ("--enable-lazy-preload".equals(argv[i])) {
// 启动延迟加载
enableLazyPreload = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
// abi类型,一个CPU对应一个abi
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
// 解析socket name
socketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
// 未知参数会抛出异常
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
// 没有指定ABI参数会抛出异常
if (abiList == null) {
throw new RuntimeException("No ABI list supplied.");
}
// 注册Zygote的socket监听端口,用来接收启动应用程序的消息
zygoteServer.registerServerSocketFromEnv(socketName);
// In some configurations, we avoid preloading resources and classes eagerly.
// In such cases, we will preload things prior to our first fork.
if (!enableLazyPreload) {
// 没有启用延迟加载的情况
.....省略一些log打印
// 执行预加载操作,包括系统预加载类、Framework资源和openGL资源
preload(bootTimingsTraceLog);
.....省略一些log打印
} else {
// 延迟加载的情况,reset 线程的优先级
Zygote.resetNicePriority();
}
// Do an initial gc to clean up after startup
......省略log打印
// 官方注释:运行几个指定的GC,尝试清除几代的软引用和可达的对象,还有别的垃圾
// 此方法只在fork()前是好使的
gcAndFinalize();
......省略log打印
// 一些与安全相关的初始化操作
Zygote.nativeSecurityInit();
// Zygote process unmounts root storage spaces.
// 目的是将整体的存储目录/storage卸载,取而代之的是挂载临时目录
// 这个动作和Android 的沙箱(隔离存储)有关
Zygote.nativeUnmountStorageOnInit();
// 呼应前面的startZygoteNoThreadCreation()方法
// 通知虚拟机,现在可以在zygote进程中创建线程了
ZygoteHooks.stopZygoteNoThreadCreation();
if (startSystemServer) {
// fork SystemServer
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
// 如果 r 为空,说明是 zygote 进程,不做任何处理,继续执行
if (r != null) {
// r 不为空,说明是孵化的子进程 systemserver,启动后直接返回
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
// 这部分是在zygote进程中执行
// 此处进入一个无限循环中,处理zygote socket接收到数据
caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
throw ex;
} finally {
// 请留意这部分,主要是给子进程用的
// 子进程中是不需要在有zygote服务的
// 所以这里的关闭理论上是为了在子进程中关闭无用的zygote服务
zygoteServer.closeServerSocket();
}
// We're in the child process and have exited the select loop. Proceed to execute the
// command.
// 除systemserver外的进程,都是在这里真正的开始main函数的
// 有没有感到疑惑,runSelectLoop是一个无线循环,要怎么才能周到这里呢?
// 走到这里为什么一定是子进程呢?
if (caller != null) {
caller.run();
}
}
看完这个main函数大家一定有很多疑问,由于有了子进程,代码的处理逻辑变得有些复杂
现将本人学习过程中的疑惑点和理解与大家分享下
知识点:fork的结果
关于fork,我们需要记住:
fork会产生和当前进程完全一样的新进程- 在
fork函数调用后- 新的进程将启动
- 并和当前进程一起从
fork函数返回
- 关于
fork的返回值- 新的进程返回
0 - 当前进程返回新进程的
pid
- 新的进程返回
疑问2:systemserver的启动
if (startSystemServer) {
// fork SystemServer
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
// 如果 r 为空,说明是 zygote 进程,不做任何处理,继续执行
if (r != null) {
// r 不为空,说明是孵化的子进程 systemserver,启动后直接返回
r.run();
return;
}
}
这段代码通过forkSystemServer来完成systemserver的fork,而forkSystemServer逻辑是:
-
执行
pid = Zygote.forkSystemServer- 请注意,执行完后会产生两个进程:
zygote和systemserver - 并且,两个进程同时开始执行,但是进程收到的
pid是不一样的 - 可参照前面的
知识点
- 请注意,执行完后会产生两个进程:
-
pid判断:// 对于systemserver进程,pid是0 if (pid == 0) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } zygoteServer.closeServerSocket(); // handleSystemServerProcess这个函数会: // 通过反射找到SystemServer的main函数 // 并包装到Runnable的run函数中 return handleSystemServerProcess(parsedArgs); } // pid不等于0,父进程,也就是zygote,返回null return null;
到这里,大家应该明白了下面这段代码的处理意义了吧:
if (r != null) {
r.run();
return;
}
疑问3:main函数最后的caller.run()怎么执行到的
应用程序的启动应该就是通过这个
caller.run(),弄明白它问题就不大了
public static void main(String argv[]) {
......
try{
......
caller = zygoteServer.runSelectLoop(abiList);
}finally {
zygoteServer.closeServerSocket();
}
......
if (caller != null) {
caller.run();
}
}
官方注释已经说了caller.run();才是子进程真正运行的地方,但是zygoteServer.runSelectLoop不是一个无线循环么,咋处理的呢?
我们来看下runSelectLoop函数:
Runnable runSelectLoop(String abiList) {
......
while (true) {
......
try {
ZygoteConnection connection = peers.get(i);
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
// We're in the child. We should always have a command to run at this
// stage if processOneCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
......
}
}
......
}
两个重要的点:
- 一个
mIsForkChild属性- 当
mIsForkChild属性为true时,会退出循环 - 并返回一个
Runnable对象
- 当
- 一个
processOneCommand函数
对于processOneCommand函数,它也有一个很重要的操作:
Runnable processOneCommand(ZygoteServer zygoteServer) {
......
// fork 子进程
pid = Zygote.forkAndSpecialize(......;
if (pid == 0) {
// in child
// 设置mIsForkChild为true
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
......
// handleChildProc这个函数会:
// 通过反射找到启动类的main函数
// 并包装到Runnable的run函数中
return handleChildProc(......);
} else {
......
return null;
}
}
在zygote的无限循环里,当监听到数据后:
- 通过
processOneCommand函数fork出了子进程,此时:- 存在两个进程正在运行:
zygote和子进程
- 存在两个进程正在运行:
- 仍然还是根据
pid来判断哪个是子进程- 对于
子进程,设置mIsForkChild属性为true - 并且
子进程将打包好的Runnable对象返回 - 对于
zygote,返回null
- 对于
这样,子进程中的runSelectLoop()便会停止循环,执行到caller.run()
书中
Android 5.0的源码是通过抛出异常的方式来执行caller.run(),也很有趣,哈哈哈
有了这些铺垫,相信下面zygote 启动应用程序这部分应该就不难了吧,哈哈哈
Zygote启动应用程序
我们已经知道,Zygote进程初始化完毕后,会通过runSelectLoop()化身为守护进程来执行启动应用程序的任务,先看下整个过程的时序图,再逐一详解吧
注册Zygote的socket
ZygoteInit类的main()函数
- 首先调用了
ZygoteServer的registerServerSocketFromEnv来创建一个本地的socket - 接着调用
runSelectLoop来进入等来socket连接的循环中
registerServerSocketFromEnv()函数如下:
void registerServerSocketFromEnv(String socketName) {
if (mServerSocket == null) {
int fileDesc;
// 拼接后的fullSocketName为ANDROID_SOCKET_zygote
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
try {
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(fullSocketName + " unset or invalid", ex);
}
try {
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);
mServerSocket = new LocalServerSocket(fd);
mCloseSocketFd = true;
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
函数通过环境变量ANDROID_SOCKET_zygote来获取socket的fileDesc。
- 这个
fileDesc(文件描述符)怎么来的呢?我们再来回顾下init.rc中的内容:service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote ...... socket zygote stream 660 root system ...... Zygote服务启动时指定了一个option用来进行socket的创建Init进程会根据这条选项来创建一个用于本地通信的socket- 并把这个
socket的fileDesc放到环境变量ANDROID_SOCKET_zygote中 - 环境变量字符串后面部分的
zygote就是option中的名称
- 得到
fileDesc后,通过LocalServerSocket()创建一个本地的socket服务,并保存到全局变量mServerSocket中
请求启动应用
Android启动一个新的进程是在ActivityManagerService中完成的,可能会有很多原因导致系统启动一个新的进程,最终在ActivityManagerService中都是通过调用方法startProcess()来实现:
final String entryPoint = "android.app.ActivityThread";
private ProcessStartResult startProcess(String hostingType, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
long startTime) {
......
final ProcessStartResult startResult;
if (hostingType.equals("webview_service")) {
......
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
}
......
}
可以看到,关键的函数为Process.start()。而Process.start()的第一个参数entryPoint就是启动后执行的Java类:android.app.ActivityThread
我们这里主要是想看下启动应用时发送给Zygote的参数有哪些?而关于应用的启动流程,后面再进行展开哈
我们跟踪下Process.start():
// class:Process
public static final ProcessStartResult start(final String processClass ......) {
return zygoteProcess.start(processClass......);
}
// class:ZygoteProcess
public final Process.ProcessStartResult start(final String processClass ......) {
return startViaZygote(processClass ......);
}
private Process.ProcessStartResult startViaZygote(final String processClass ......){
ArrayList<String> argsForZygote = new ArrayList<String>();
// --runtime-args, --setuid=, --setgid=,
// and --setgroups= must go first
argsForZygote.add("--runtime-args");
......
argsForZygote.add(processClass);
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
参数有点多,代码部分意思一下哈,嘿嘿
start()方法调用startViaZygote()方法来启动应用startViaZygote()首先将应用进程的启动参数保存到argsForZygote集合中- 然后调用
zygoteSendArgsAndGetResult方法将应用程序进程的启动参数发送的Zygote进程 zygoteSendArgsAndGetResult()方法中会调用openZygoteSocketIfNeeded()方法用来创建用于通信的socket,整理后的代码是这样的:public static final String ZYGOTE_SOCKET = "zygote"; public static final LocalSocketAddress address = new LocalSocketAddress(ZYGOTE_SOCKET, LocalSocketAddress.Namespace.RESERVED); final LocalSocket zygoteSocket = new LocalSocket(); zygoteSocket.connect(address);- 对于
Local Socket使用字符串作为地址就可以通信,灰常方便啊
- 对于
socket连接建立完后,zygoteSendArgsAndGetResult利用这个socket进行启动参数的写入:writer.write(Integer.toString(args.size())); writer.newLine(); for (int i = 0; i < sz; i++) { String arg = args.get(i); writer.write(arg); writer.newLine(); } writer.flush();
到这里,请求应用启动的参数就发送给Zygote进程了,我们接下来看下Zygote的处理过程
处理启动应用的请求
Zygote进程通过runSelectLoop()来监听和处理启动应用的请求,我们看下runSelectLoop(),注释比较详细哈:
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
// 将socket zygote的文件描述符添加到fds集合中
// 此处需要留意,mServerSocket是集合中第一个元素
fds.add(mServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
// 动态生成pollFds数组,作为poll的监听参数
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 {
// 通过poll对pollFds数组中的文件进行事件监听
// -1 表示阻塞直到有数据进来
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) {
// i=0 说明是 mServerSocket
// 意味着客户端发起了连接请求
// 按照LocalServerSocket的流程,需要创建一个LocalSocket来用于客户端的通信
// acceptCommandPeer()就是按照这个流程来创建ZygoteConnection对象
// 其实真正的通信对象还是LocalSocket
// 然后将通信对象的FD添加到fds集合中
// 大家没必要纠结这部分的处理方式,当做是LocalSocket的标准用法就可以啦
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
try {
// 这里才是真正的处理部分
ZygoteConnection connection = peers.get(i);
// 执行对应connection的processOneCommand函数
// 函数中会读取解析参数并进行子进程fork的相关操作
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
......
// 子进程,退出selectLoop循环
return command;
} else {
......
// zygote进程,关闭移除connection
if (connection.isClosedByPeer()) {
connection.closeSocket();
peers.remove(i);
fds.remove(i);
}
}
} catch (Exception e) {
if (!mIsForkChild) {
// 异常后的清除操作
......
ZygoteConnection conn = peers.remove(i);
conn.closeSocket();
fds.remove(i);
} else {
......
}
} finally {
......
mIsForkChild = false;
}
}
}
}
}
从上面的代码中我们知道,对于消息真正的处理是在ZygoteConnection.processOneCommand函数中,我们来重点看下:
processOneCommand函数
函数分为了三部分:
- 解析启动参数
- 子进程
fork - 子进程初始化
我们来逐一看下
解析启动参数
参数解析的流程是
- 先通过
readArgumentList方法从socket连接中读取多个参数行- 参数行的样式是
--setuid=1 - 行与行之间以
\r、\n或者\r\n分割
- 参数行的样式是
- 读完后再调用
Arguments类的parseArgs方法将数据解析为参数列表- 具体的参数意义,大家可以参考
Arguments类的注释
- 具体的参数意义,大家可以参考
解析完参数后,processOneCommand还会对解析出的参数进行检查和设置:
...... 省略一些参数属性判断
// 检查客户端手有权限指定进程的用户ID、组ID
// 如果是 root 进程,可以任意指定
// 如果是 sys 进程,需要在ro.factorytest值 > 0时可以指定
applyUidSecurityPolicy(parsedArgs, peer);
// 判断是否具有invoke-with的执行权限
applyInvokeWithSecurityPolicy(parsedArgs, peer);
// 如果ro.debuggable是1的话,启动JDWP协议
applyDebuggerSystemProperty(parsedArgs);
// 处理invoke-with属性
applyInvokeWithSystemProperty(parsedArgs);
细节就不展开了哈
fork子进程
参数检查无误后,processOneCommand函数会调用forkAndSpecialize方法来fork子进程:
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
parsedArgs.instructionSet, parsedArgs.appDataDir);
forkAndSpecialize最终是通过native层的ForkAndSpecializeCommon来完成fork的,我们这里只是简单介绍下函数的主要工作哈:
- 通过
fork创建子进程 - 在子进程中挂载
emulate storage - 在子进程中设置用户ID、组ID、和进程所属的组
- 在子进程中通过
setrlimit系统调用设置进程的系统资源限制 - 在子进程中通过
capset系统调用设置进程的权限 - 在子进程中通过
selinux_android_setcontext设置应用进程的安全上下文
从native层返回Java层后
- 如果
pid==0,表示处于子进程中,执行handleChildProc函数 - 如果
pid!=0,表示在Zygote进程中,执行handleParentProc函数,并返回null
这部分代码在前面的疑问中其实已经展示过了哈:
if (pid == 0) {
// in child
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
return handleChildProc(parsedArgs, descriptors, childPipeFd,parsedArgs.startChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
handleParentProc(pid, descriptors, serverPipeFd);
return null;
}
子进程的初始化
Zygote进程fork出子进程后,调用handleChildProc方法来完成子进程的初始化工作。
handleChildProc方法首先关闭了监听用的socket和从Zygote中继承的文件描述符closeSocket(); ...... for (FileDescriptor fd: descriptors) { IoUtils.closeQuietly(fd); } ......- 接下来,根据启动参数是否有
--runtime-init以及--invoke-with来判断如何初始化if (parsedArgs.invokeWith != null) { WrapperInit.execApplication(......); ...... } else { ...... return ZygoteInit.zygoteInit(......); ...... }- 启动
apk应用都会带有--runtime-init参数,但是--invoke-with通常为null --invoke-with不为null将会通过exec的方式启动app_process来执行Java类- 正常情况下会调用
ZygoteInit.zygoteInit方法
- 启动
ZygoteInit.zygoteInit方法又调用了三个方法:RuntimeInit.commonInit()、ZygoteInit.nativeZygoteInit()、RuntimeInit.applicationInit(),最后return一个Runnable的对象给调用者commonInit():一些通用配置的简单初始化:- 设置
KillApplicationHandler为默认的UncaughtExceptionHandler - 设置时区
- 设置
http.agent属性,用于HttpURLConnection - 重置
Android的Log系统 - 通过
NetworkManagementSocketTagger设置socket的tag,用于流量统计
- 设置
nativeZygoteInit()- 这是一个本地方法,但是很重要,因为它做了这么一件事
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz) { gCurRuntime->onZygoteInit(); }gCurRuntime?有木有很眼熟,它其实就是AppRuntime的实例,大家可以往上翻看AndroidRuntime初始化的部分- 然后调用了
onZygoteInit,我们看下AppRuntime的函数实现:
virtual void onZygoteInit() { sp<ProcessState> proc = ProcessState::self(); proc->startThreadPool(); }- 这段代码是不是也眼熟,哈哈哈,在
Binder章节我们已经学过了 - 主要是初始化
Binder的使用环境,这样,应用进程就可以使用Binder了
applicationInit():前两步初始化完成后,会执行并返回这个函数- 设置虚拟机的
HeapUtilization为0.75f - 设置当前的
SDKVersion - 除了上面的两个,最重要的是调用了
findStaticMain()函数来查找Java类的main方法,并包装成Runnable的形式
protected static Runnable findStaticMain(......) { Class<?> cl = Class.forName(className, true, classLoader); Method m = cl.getMethod("main", new Class[] { String[].class }); int modifiers = m.getModifiers(); return new MethodAndArgsCaller(m, argv); } static class MethodAndArgsCaller implements Runnable { private final Method mMethod; private final String[] mArgs; ...... public void run() { ...... mMethod.invoke(null, new Object[] { mArgs }); ...... } }- 到这里,函数的调用就结束了
- 这都是一些
Java反射的相关知识啦,不清楚的话要学习哟
- 设置虚拟机的
processOneCommand函数的终点
processOneCommand函数执行完会出现两种情况:
- 对于
子进程,返回都打包好的MethodAndArgsCaller实例 - 对于
Zygote,继续循环
此时,子进程的runSelectLoop会退出循环,并返回MethodAndArgsCaller实例,最终执行到:
caller = zygoteServer.runSelectLoop(abiList);
if (caller != null) {
caller.run();
}
预加载系统类和资源
为了加快应用程序的启动,Android会把系统常用的Java类和一部分Framework的资源放在Zygote进程中进行预加载。这些预加载的类和资源在所有经zygote进程fork出的子进程中是共享的。
如图所示:
预加载操作是在ZygoteInit.main()函数中,通过preload()函数来完成的,代码如下:
static void preload(TimingsTraceLog bootTimingsTraceLog) {
......
// 设置软引用保护,避免被GC回收掉
beginIcuCachePinning();
......
// 加载系统类
preloadClasses();
......
// 加载系统资源
preloadResources();
......
//
nativePreloadAppProcessHALs();
......
// 加载OpenGL资源
preloadOpenGL();
// 加载一些共享so库,其实就三个:android、compiler_rt、jnigraphics
preloadSharedLibraries();
// 加载字体资源
preloadTextResources();
......
// 加载webview相关资源
WebViewFactory.prepareWebViewInZygote();
// 取消软引用保护
endIcuCachePinning();
// 初始化JCA安全相关的参数
warmUpJcaProviders();
Log.d(TAG, "end preload");
sPreloadComplete = true;
}
注释比较详细,我们重点看下Java类、系统资源和共享库的加载
预加载Java类
Android将所有需要预加载的Java类放在文本文件preload-classes中。
在9.0源码中的文件存放路径是在frameworks/base/config/preloaded-classes,内容格式如下:
#
# This file has been derived for mainline phone (and tablet) usage.
#
......
android.animation.Animator
android.app.ActivityThread
android.app.FragmentManager
......
preloadClasses()函数的作用就是解析这个文件,并加载里面声明的的所有类,函数如下:
private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
private static void preloadClasses() {
......
InputStream is = new FileInputStream(PRELOADED_CLASSES);
......
BufferedReader br = new BufferedReader(new InputStreamReader(is), 256);
int count = 0;
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.equals("")) {
continue;
}
......
Class.forName(line, true, null);
......
}
}
到此,对于这些类,虚拟机也仅仅是完成了装载而已。只有在首次主动使用时,才会真正的进行类的初始化
preloaded-classes声明了这么多类,它是怎么生成的呢?
生成流程是:
- 首先有一个
frameworks/base/tools/preload/WritePreloadedClassFile.java类,它对应的module是preload - 其次还有有一个
frameworks/base/tools/preload/20100223.compiled文件 - 然后通过
mmm frameworks/base/tools/preload可以编译出preload.jar - 最后通过
java -Xss4M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled来解析,解析的结果是得到一个包含所有Java类的列表,列表的内容包括:- root对象
- 被几个进程装载
- 装载的平均时长
- 最后文件生成路径在
/frameworks/base/preloaded-classes
20100223.compiled是源码自带的,那么问题就来了,.compiled这种类型的文件是怎么生成的呢?
- 首先,有一个
frameworks/base/tools/preload/Compile.java类,也是在preload中,它是用来分析log的preload信息的 - 然后我们可以通过
java -Xss512M -cp out/host/linux-x86/framework/preload.jar Compile logcat.txt 20201111.compiled来生成分析结果,也就是.compiled文件
预加载系统资源
预加载系统资源的函数是preloadResources(),主要代码如下:
private static void preloadResources() {
......
mResources = Resources.getSystem();
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
......
TypedArray ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_drawables);
int N = preloadDrawables(ar);
ar.recycle();
......
ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_color_state_lists);
N = preloadColorStateLists(ar);
ar.recycle();
......
}
mResources.finishPreloading();
......
}
预加载Framework的资源核心的两个函数是:
preloadDrawables:加载drawable资源preloadColorStateLists:加载color状态定义资源
具体资源加载过程后面再详细学习,我们先看下预加载资源的定义,在源码目录frameworks/base/core/res/res/values/arrays.xml:
<array name="preloaded_drawables">
<item>@drawable/action_bar_item_background_material</item>
<item>@drawable/activated_background_material</item>
</array>
<array name="preloaded_color_state_lists">
<item>@color/primary_text_dark</item>
<item>@color/primary_text_dark_disable_only</item>
</array>
预加载共享库
预加载共享库的函数是preloadSharedLibraries(),代码如下:
private static void preloadSharedLibraries() {
Log.i(TAG, "Preloading shared libraries...");
System.loadLibrary("android");
System.loadLibrary("compiler_rt");
System.loadLibrary("jnigraphics");
}
一共是是三个so文件:
libandroid.solibcompiler_rt.solibjnigraphics.so
结语
Zygote进程的学习到这里就结束啦,又进一步加深了对Android的认知,哈哈哈哈!
为啥这里叒叒叒叒有结语呢?哈哈哈哈,好奇宝宝可以看这里!
知识终归是知识,技巧还是有待磨练滴!
下一篇开始学习Android 的资源管理,加油~~