Skip to content

app_process入口

拆解 app_process 中 VM 参数、Zygote 参数、应用类参数和 AppRuntime 回调的分层入口。

基于android-17.0.0_r1
AndroidZygoteapp_process源码阅读

app_process入口 ​

app_process 是 native 入口,但它不直接创建 Zygote 或启动应用类。它先计算可改写的 argv 区域,识别哪些参数属于 ART、哪些属于 app_process,再由 AppRuntime 把控制权交给 AndroidRuntime::start()。这篇文章只讲这一层的参数和回调边界;Zygote 如何预加载、fork SystemServer 和运行 socket 循环留在 SS001 与后续文章。

1. AppRuntime状态 ​

源码文件:frameworks/base/cmds/app_process/app_main.cpp

相关类型:AppRuntime

cpp
class AppRuntime : public AndroidRuntime {
public:
    AppRuntime(char* argBlockStart, const size_t argBlockLength)
        : AndroidRuntime(argBlockStart, argBlockLength), mClass(NULL) {}

    void setClassNameAndArgs(const String8& className,
                             int argc, char* const* argv) {
        mClassName = className;
        for (int i = 0; i < argc; ++i) {
            mArgs.add(String8(argv[i]));
        }
    }

    String8 mClassName;
    Vector<String8> mArgs;
    jclass mClass;
};

AppRuntime 继承 AndroidRuntime,新增三项状态:启动类名、传给该类 main 的参数、JNI 全局类引用。setClassNameAndArgs() 复制字符串,而不是保存原始 argv 指针;这是因为后面 setArgv0() 会覆盖整个参数块来设置进程名,原始参数内存不能再作为稳定存储。

2. argv可改写区域 ​

源码文件:frameworks/base/cmds/app_process/app_main.cpp

相关函数:computeArgBlockSize()、AppRuntime 构造函数

cpp
static size_t computeArgBlockSize(int argc, char* const argv[]) {
    // Linux exec 通常把 argv 放在连续参数区;app_process 用它改写 argv[0]。
    uintptr_t start = reinterpret_cast<uintptr_t>(argv[0]);
    uintptr_t end = reinterpret_cast<uintptr_t>(argv[argc - 1]);
    end += strlen(argv[argc - 1]) + 1;
    return end - start;
}

AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));

这个尺寸只用于进程名改写,不是 Java 参数总长度限制,也不是 Zygote socket 缓冲区大小。源码注释还指出普通应用由 Zygote fork 出来,命令行空间沿用 Zygote 的大小;如果只按当前短参数重新计算,后续设置较长 nice name 可能被截断。

3. 参数分层算法 ​

源码文件:frameworks/base/cmds/app_process/app_main.cpp

main() 把参数分成两段:遇到第一个非 - 参数前,内容交给 VM;随后跳过未使用的 parent dir,再解析 app_process 内部选项。-cp/-classpath 是例外,它们虽然带一个后续非短横线参数,仍被视为 VM 选项。

cpp
const char* spaced_commands[] = { "-cp", "-classpath" };
bool known_command = false;
int i;
for (i = 0; i < argc; i++) {
    if (known_command) {
        runtime.addOption(strdup(argv[i]));
        known_command = false;
        continue;
    }
    for (const char* command : spaced_commands) {
        if (strcmp(argv[i], command) == 0) known_command = true;
    }
    if (argv[i][0] != '-') break;
    if (argv[i][1] == '-' && argv[i][2] == 0) {
        ++i; // Skip the VM separator.
        break;
    }
    runtime.addOption(strdup(argv[i]));
}

这段循环的消费者是 AndroidRuntime,不是 Zygote。-- 是 VM 参数结束标记;没有它时,第一个非选项参数也会结束 VM 参数阶段。若把 --start-system-server 当成 VM 选项,Zygote 将收不到启动 SystemServer 的内部标志。

4. 启动分支 ​

源码文件:frameworks/base/cmds/app_process/app_main.cpp

cpp
bool zygote = false;
bool startSystemServer = false;
bool application = false;
String8 niceName;
String8 className;

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;
    }
}

随后分支如下:

Zygote 分支没有 className,因此会创建 dalvik-cache 目录、读取 64/32 位 ABI 属性并把剩余参数交给 ZygoteInit.main()。应用/工具分支必须有 className,并把 application 或 tool 放入 args,供 RuntimeInit 判断启动模式。

5. Runtime回调 ​

源码文件:frameworks/base/cmds/app_process/app_main.cpp

5.1 onVmCreated ​

cpp
void AppRuntime::onVmCreated(JNIEnv* env) {
    if (mClassName.empty()) return; // Zygote 不在这里查启动类。
    char* slashClassName = toSlashClassName(mClassName.c_str());
    mClass = env->FindClass(slashClassName);
    free(slashClassName);
    mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
}

只有非 Zygote 模式需要提前 FindClass。源码注释解释了原因:如果等到 onStarted() 从 boot class 的 native 方法中查找,JNI 的类加载器上下文可能看不到 CLASSPATH 中的普通工具类。查找失败会记录错误,随后由后续启动阶段暴露;这里不能把 mClass 非空当作应用已经运行。

5.2 启动回调 ​

cpp
void AppRuntime::onStarted() {
    sp<ProcessState> proc = ProcessState::self();
    proc->startThreadPool();
    AndroidRuntime* ar = AndroidRuntime::getRuntime();
    ar->callMain(mClassName, mClass, mArgs);
    IPCThreadState::self()->stopProcess();
    hardware::IPCThreadState::self()->stopProcess();
}

void AppRuntime::onZygoteInit() {
    sp<ProcessState> proc = ProcessState::self();
    proc->startThreadPool();
}

两条回调的消费者不同:应用/工具启动后调用目标类的 main,返回后停止 kernel Binder 和 hwbinder 进程状态;Zygote 只启动 Binder 线程池,不调用应用类 main。线程池启动成功也不等于目标服务已注册,它只是为后续 Binder 调用准备消费者。

6. 错误与结束路径 ​

  • ABI 属性缺失:Zygote 分支调用 LOG_ALWAYS_FATAL,尚未进入 ZygoteInit.main()。
  • 没有 className 且没有 --zygote:打印 usage 并 fatal,属于入口协议错误。
  • Java class 查找失败:onVmCreated() 记录错误,后续 callMain 不能获得有效目标类。
  • 应用 main 返回:onStarted() 停止 Binder/hwbinder 状态后结束进程;这不是 Zygote 的常驻路径。
  • Zygote 退出:onExit() 关闭 kernel Binder 和 hwbinder 进程状态,再调用 AndroidRuntime::onExit()。

7. 源码导航 ​

命令输入是 Android 17 checkout 中的相对路径,输出用于定位参数分支、回调和错误文本;它们不会启动设备。

bash
rg -n "computeArgBlockSize|class AppRuntime|onVmCreated|onStarted|onZygoteInit|onExit" \
  frameworks/base/cmds/app_process/app_main.cpp

rg -n "--zygote|--start-system-server|--application|--nice-name|runtime.start" \
  frameworks/base/cmds/app_process/app_main.cpp

rg -n "AndroidRuntime::start|callMain|setArgv0|startThreadPool" \
  frameworks/base/cmds/app_process frameworks/base/core/cmds

下一篇将进入 AndroidRuntime::start(),解释 VM 创建、JNI 注册与 CallStaticVoidMethod 如何把控制权交给 ZygoteInit.main();不会重复本文的参数扫描循环。