或许你会有疑问:都2025年了,为什么还要写这样的文章?网上类似的资料早已不计其数。
这个想法其实源于我在"极客时间"上看到的一句话, "从学习框架,到向框架学习",作为一名从事Android应用开发多年的工程师,我希望对自己的知识做一次全新的系统性梳理。恰恰是因为 当前Android领域的大环境不如往昔,这类文章看似"无用",我才更下定决心要写------为自己作一次彻底的沉淀与总结。 况且,关于Android 15的深入探讨目前相对较少,没有充分的调研与实践,又如何能言之有物?
环境准备
AOSP的源码有120多G,我们重点关注 frameworks/base 模块,没必要全部下载
php
// 清华大学AOSP源,framework 部分
git clone https://aosp.tuna.tsinghua.edu.cn/platform/frameworks/base -b android-15.0.0_r1
IDE就用VSCode即可
Android系统的启动
这一部分不是本文的重点,但是要讲Zygote又绕不过,还是简单介绍一下
- 启动电源,引导程序BootLoader开始执行
- Linux内核启动,找到init.rc文件,启动init进程
- init.rc文件中,就有我们今天的主角Zygote,app_main.cpp 的main方法会被调用
init.rc文件
kotlin
//system/core/rootdir/init.rc
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
//...
其中 ${ro.zygote}
会被替换成 ro.zyogte 的属性值,这个是由不同的硬件厂商自己定制的。 有四个值:zygote32、zygote64、zygote32_64、zygote64_32 ,也就是说可能有四种 .rc 文件,分别是:
- init.zygote32.rc:zygote 进程对应的执行程序是 app_process(纯 32bit 模式)。
- init.zygote64.rc:zygote 进程对应的执行程序是 app_process64(纯 64bit 模式)。
- init.zygote32_64.rc:启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process32(主模式)、app_process64。
- init.zygote64_32.rc:启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process64(主模式)、app_process32。
app_main.cpp详解
main函数入口
arduino
int main(int argc, char* const argv[])
{
//*** 前面省略 ***
// 创建AppRuntime对象
// computeArgBlockSize 是计算整个参数块的大小
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
//*** 后面省略 ***
}
新建了一个AppRuntime 对象,并且对 main方法的入参 进行了大小计算 computeArgBlockSize
,原因是后面要对进程的名字进行修改,"app_process" -> "zygote", 亦或是 "zygote" -> "system_server" 进程名放在argv[0]中,知道参数大小,防止修改内存越界,同时准确的清空原参数块,写入新名称.
入参上下文解析
arduino
// Parse runtime arguments. Stop at first unrecognized option.
// 是否以 zygote 模式启动
bool zygote = false;
// 是否启动系统服务器
bool startSystemServer = false;
// 是否以应用模式启动
bool application = false;
String8 niceName;
// 要启动的 Java 类名
String8 className;
++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 = (arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className = arg;
break;
} else {
--i;
break;
}
}
典型的app_process 的命令行参数
ini
# Zygote 模式启动系统服务器
app_process -Xzygote /system/bin --zygote --start-system-server --nice-name=system_server com.android.server.SystemServer
# 应用模式启动
app_process -Xapplication /system/bin --application --nice-name=com.example.app com.example.MainActivity
修改进程名
less
if (!niceName.empty()) {
runtime.setArgv0(niceName.c_str(), true /* setProcName */);
}
"app_process" -> "zygote"
Android Runtime 启动
Android开发者似乎不太常用Runtime
这个词,iOS里面更常用,其实Android也是有的呀,只是叫法不同罢了! 根据 zygote 和 className 的不同,进入ZygoteInit 或者 RuntimeInit 的 main,init.rc 启动的 zygote 是 true。
scss
// 如果是启动zygote
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (!className.empty()) {
// 如果是启动应用程序
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.");
}
Android Runtime start函数
arduino
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {
static const String8 startSystemServer("start-system-server");
// Whether this is the primary zygote, meaning the zygote which will fork system server.
bool primary_zygote = false;
/*
* 'startSystemServer == true' means runtime is obsolete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
for (size_t i = 0; i < options.size(); ++i) {
if (options[i] == startSystemServer) {
primary_zygote = true;
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}
}
//...
标记当前的 Zygote 进程是否是Primary Zygote
,64位系统,存在一个主Zygote和次Zygote。
-
主 Zygote: 负责启动 system_server 进程,并孵化大部分的应用进程。它通常处理 64位 的应用(在64位设备上)。
-
次 Zygote (Secondary Zygote): 在64位设备上,它负责孵化 32位 的应用进程,为的就是兼容32位应用。
primary_zygote 这个标志就是用来区分这两种 Zygote 的。
为何需要区分主次 Zygote?
-
解决 64 位设备兼容性问题
-
单一 Zygote 同时加载 32/64 位库会导致:
- 内存浪费(冗余加载)
- 库冲突(如
libc
的 32/64 位版本不兼容)。
-
-
优化系统性能
- 并行孵化:主次 Zygote 独立处理请求,避免 32/64 位应用竞争资源。
- 启动加速:预加载按需分配,减少 Zygote 自身启动时间(主 Zygote 无需加载 32 位库)。
-
架构清晰性
- 主 Zygote 专注系统服务和高性能应用,次 Zygote 处理兼容性需求,职责分离提升稳定性
创建ART虚拟机
startVm函数这里我们先不展开,另外Zygote创建的虚拟机,是可以被所有fork出来的进程共享的,所以我们启动的应用程序就不需要做这些工作了~
ini
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
return;
}
注册Jni函数
bash
/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
解析要调用的Java类名
scss
char* slashClassName = toSlashClassName(className != NULL ? className : "");
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 {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
- com/android/internal/os/ZygoteInit :Zygote 进程的入口类
- android/app/ActivityThread :应用程序主线程的入口类
跨越JNI边界 :当env->CallStaticVoidMethod()
调用ZygoteInit.main()
,标志着C++世界到Java世界的控制权移交,Zygote的核心孵化逻辑自此由Java层掌控。
Java世界
ZygoteInit.java
直接看main函数的实现
java
ZygoteServer zygoteServer = null;
// Mark zygote start. This ensures that thread creation will throw
// an error.
ZygoteHooks.startZygoteNoThreadCreation();
- 先创建了一个ZygoteServer对象,赋值null。
ZygoteServer
是 Zygote 的核心类,负责创建 Server Socket、监听和处理来自 ActivityManagerService 的命令。对象稍后会初始化。 ZygoteHooks.startZygoteNoThreadCreation()
,这个方法禁止Zygote进程创建线程,目的是保证在zygote预加载和初始化阶段,避免并发问题,同时为fork做准备,我们知道fork进程的时候,如果有其他线程会有潜在的状态不一致的问题。(本质fork只复制调用fork的那一个线程,来创建一个全新的子进程, Linux中线程和进程都是使用task_struct来描述)。- 禁止子线程的创建,避免fork 多线程的问题,是zygote的很重要的设计哲学
学框架,向框架学习 ==》
fork只保证当前调用的线程,如果有其他线程有锁竞争,会有安全隐患。在Linux内核看来,进程和线程都是task_struct来描述,区别只是资源共享与否
perl
Tips: 我记得有一些OOM监控框架,在发生OOM时,会fork子进程来收集Hprof文件,那这个fork会有多线程的问
题吗? 我想大概率也是有的,只不过此时只需要尽快将Hprof文件,dump出来,然后裁剪上传服务器,App进程就可
以死了,所以影响也不大。
Preload
又到了很重要的知识点,Preload
scss
// In some configurations, we avoid preloading resources and classes eagerly.
// In such cases, we will preload things prior to our first fork.
if (!enableLazyPreload) {
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preload(bootTimingsTraceLog);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
}
如果没有开启延迟预加载,就会进行预加载,那预加载什么呢?进入preload函数看一下
scss
static void preload(TimingsTraceLog bootTimingsTraceLog) {
bootTimingsTraceLog.traceBegin("BeginPreload");
// 回调开始preload了
beginPreload();
bootTimingsTraceLog.traceEnd(); // BeginPreload
bootTimingsTraceLog.traceBegin("PreloadClasses");
preloadClasses();
bootTimingsTraceLog.traceEnd(); // PreloadClasses
bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders");
cacheNonBootClasspathClassLoaders();
bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders
bootTimingsTraceLog.traceBegin("PreloadResources");
Resources.preloadResources();
bootTimingsTraceLog.traceEnd(); // PreloadResources
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs");
nativePreloadAppProcessHALs();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver");
maybePreloadGraphicsDriver();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
preloadSharedLibraries();
preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
// for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
endPreload();
warmUpJcaProviders();
Log.d(TAG, "end preload");
sPreloadComplete = true;
}
- beginPreload() :回调通知开始预加载
- preloadClasses() :预加载系统类,这些类定义在 /system/etc/preloaded-classes 文件中
- cacheNonBootClasspathClassLoaders() :缓存非启动类路径的类加载器
- Resources.preloadResources() :预加载系统资源,包括 drawable、layout 等资源文件
- nativePreloadAppProcessHALs() :预加载应用进程需要的硬件抽象层(HAL)
- maybePreloadGraphicsDriver() :预加载图形驱动,优化图形性能
- preloadSharedLibraries() :预加载共享库,如 android、jnigraphics 等
- preloadTextResources() :预加载文本资源,如 Hyphenator、TextView 的字体缓存等
- WebViewFactory.prepareWebViewInZygote() :在 Zygote 进程中准备 WebView
- endPreload() :结束预加载阶段,执行清理工作
- warmUpJcaProviders() :预热 Java 加密体系结构(JCA)提供者
预加载的东西也比较多,但是干的事情都是类似的,我这里拿preloadClasses
、preloadResources
、 WebViewFactory.prepareWebViewInZygote
来举例,其他的大家可以自行研究
proloadClasses
csharp
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is;
try {
is = new FileInputStream(PRELOADED_CLASSES);
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
return;
}
BufferedReader br =
new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
Class.forName(line, true, null);
runtime.preloadDexCaches();
Android 基础框架类:
-
android.R 相关资源类
-
android.accessibilityservice 无障碍服务相关类
-
android.accounts 账户管理相关类
-
android.animation 动画相关类
-
android.annotation 注解相关类
-
android.apex APEX(Android Pony EXpress)相关类
Android 应用组件类:
-
android.app.Activity 及其相关内部类
-
android.app.ActivityManager 活动管理器相关类
-
android.app.ActivityThread 应用主线程相关类
-
android.app.AlarmManager 闹钟管理器相关类
-
android.app.AppOpsManager 应用操作管理器相关类
-
android.app.Application 应用程序相关类
cacheNonBootClasspathClassLoaders
缓存非启动类路径加载器,减少fork后查找开销
Resources.preloadResources
ini
@UnsupportedAppUsage
public static void preloadResources() {
try {
// 1. 获取系统 Resources 实例
final Resources sysRes = Resources.getSystem();
// 2. 开始预加载
sysRes.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
long startTime = SystemClock.uptimeMillis();
// 3. 预加载 Drawable 资源
TypedArray ar = sysRes.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
int numberOfEntries = preloadDrawables(sysRes, ar);
ar.recycle();
Log.i(TAG, "...preloaded " + numberOfEntries + " resources in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
// 4. 预加载 ColorStateList 资源
ar = sysRes.obtainTypedArray(
com.android.internal.R.array.preloaded_color_state_lists);
numberOfEntries = preloadColorStateLists(sysRes, ar);
ar.recycle();
Log.i(TAG, "...preloaded " + numberOfEntries + " resources in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
}
// 5. 完成预加载
sysRes.finishPreloading();
} catch (RuntimeException e) {
Log.w(TAG, "Failure preloading resources", e);
}
}
WebViewFactory.prepareWebViewInZygote
csharp
/**
* Perform any WebView loading preparations that must happen in the zygote.
* Currently, this means allocating address space to load the real JNI library later.
*/
public static void prepareWebViewInZygote() {
try {
WebViewLibraryLoader.reserveAddressSpaceInZygote();
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the zygote.
Log.e(LOGTAG, "error preparing native loader", t);
}
}
csharp
/**
* Reserve space for the native library to be loaded into.
*/
static void reserveAddressSpaceInZygote() {
System.loadLibrary("webviewchromium_loader");
long addressSpaceToReserve;
if (VMRuntime.getRuntime().is64Bit()) {
// On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
addressSpaceToReserve = 1 * 1024 * 1024 * 1024;
} else if (VMRuntime.getRuntime().vmInstructionSet().equals("arm")) {
// On 32-bit the address space is fairly scarce, hence we should keep it to a realistic
// number that permits some future growth but doesn't hog space. For ARM we use 130MB
// which is roughly what was calculated on older OS versions. The size has been
// growing gradually, but a few efforts have offset it back to the size close to the
// original.
addressSpaceToReserve = 130 * 1024 * 1024;
} else {
// The number below was obtained for a binary used for x86 emulators, allowing some
// natural growth.
addressSpaceToReserve = 190 * 1024 * 1024;
}
sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
if (sAddressSpaceReserved) {
if (DEBUG) {
Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
}
} else {
Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
}
}
- 基本功能:
- 在 Zygote 进程中为 WebView 的原生库预留地址空间
- 加载 webviewchromium_loader 原生库
- 地址空间分配策略:
- 64位系统:预留 1GB 空间,因为地址空间充足
- 32位 ARM 系统:预留 130MB 空间,考虑到地址空间稀缺性和历史经验值
- 其他 32位系统(如 x86):预留 190MB 空间,基于模拟器二进制文件的需求
- 实现细节:
- 通过 System.loadLibrary() 加载 WebView 的加载器库
- 使用 VMRuntime 检测系统架构(64位/32位)和指令集
- 调用 native 方法 nativeReserveAddressSpace() 实际预留空间
- 使用 sAddressSpaceReserved 标志记录预留是否成功
Android性能优化之道
书中有谈到用不到WebView的进程,可以通过查找maps文件寻址,通过munmap将预分配的虚拟内存释放掉,释放的就是上面Zygote reserveAddressSpaceInZygote
的虚拟内存。
为何要Preload
- 提升应用启动性能:应用进程从 Zygote fork 出来时,这些类已经加载完成,可以直接使用
- 节省内存:所有应用共享这些预加载的类,避免每个应用都单独加载一份
- 优化系统响应:关键系统服务和常用组件类预加载后可以更快响应
preloadDexCaches
最后还将加载的这些类的DEX文件,缓存到内存中,同样Zygote可以和所有进程共享,节约内存,也加快了应用启动性能
学框架,向框架学习 ==》
预加载在应用开发中也经常使用,但是应用开发比较难把控的是,当前硬件设备的 cpu的负载,内存水位,网络IO情况,可能99%的情况做预加载是正优化,但是会有1%的情况是负优化
gcAndFinalize()
csharp
/**
* Runs several special GCs to try to clean up a few generations of softly- and final-reachable
* objects, along with any other garbage. This is only useful just before a fork().
*/
private static void gcAndFinalize() {
ZygoteHooks.gcAndFinalize();
}
在创建新进程前清理内存,减少不必要的对象复制,注释明确指出这个操作只在 fork() 之前使用才有意义。
Android 15在此阶段引入Generational GC,有一些测试数据,Zygote堆内存清理效率提升50%(实测Pixel 7 Pro预加载后内存下降18%)。
Zygote.initNativeState(isPrimaryZygote)
- 初始化 Zygote 进程的本地(native)状态,包括:
- 从环境变量中获取 socket 文件描述符
- 初始化安全属性
- 根据需要卸载存储设备
- 加载必要的性能配置信息
- 该方法通过调用 native 方法 nativeInitNativeState() 实现具体功能,接收一个布尔参数 isPrimary :
- 当 isPrimary 为 true 时,表示这是主 Zygote 进程
- 当 isPrimary 为 false 时,表示这是辅助 Zygote 进程(zygote_secondary)
ZygoteHooks.stopZygoteNoThreadCreation()
前面我们提到禁用了线程的创建,初始化到此时,需要预加载的,初始化的都准备就绪,此时可以放开了线程的创建限制。 是如何禁止线程的创建的呢?其实也很简单,就是一个全局变量 gZygoteNoThreadSection
,检测为true,thread创建会被拦截
rust
// art/runtime/native/java_lang_Thread.cc
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, ...) {
if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) {
env->ThrowNew(env->FindClass("java/lang/InternalError"), "Cannot create threads in zygote");
return;
}
// 正常创建线程的逻辑...
}
创建ZygoteServer
ini
zygoteServer = new ZygoteServer(isPrimaryZygote);
来到了Zygote中一个核心的对象,它负责Zygote进程的服务器功能,主要有如下职责
- 监听来自客户端的连接请求
- 处理应用进程创建命令
- 管理 USAP(Unspecialized App Process)池
这里又有一个新的知识点USAP
,暂时可以理解为Zygote预先初始化了一些进程,放入池子中,当有新的进程创建请求来到时候,从池子中直接分配, 目的肯定是空间换时间,加速应用的启动。
学框架,向框架学习 ==》
典型的池化的应用,线程池,对象池,进程池,空间换时间
startSystemServer
启动system_server进程,也是zygote很重要的职责之一,这里先提一下,后面文章会详细分析~
scss
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();
return;
}
}
runSelectLoop
scss
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
终于到了我们熟知的监听循环了,由zygoteServer来负责,ZygoteServer也有很多有意思的可以研究的,我们下一篇继续。