Android启动流程_Zygote阶段

news2024/11/26 15:00:42

前言

上一篇文档中我们描述了 Android 启动中的 init 启动部分,本片文档将会继续 Android 启动流程的逻辑,继续梳理 Zygote 部分功能。

说明框架

对于 Zygote 进程,要从以下框架说明:

第一点,编译,zygote 运行的二进制编译文件

​ zygote32、zygote64 文件对应 app_process32、app_precess64 文件区别,对应的功能。

第二点,功能,zygote 的重要功能描述

从上面两个点展开说明,可以说到 AndroidRuntime 虚拟机、预加载资源、app_process 命令的使用等。

正文

对于 Zygote 进程的功能描述,准备从以下方面展开描述:

首先,会对于 Zygote 进程运行的二进制文件描述,从代码路径到编译结构展开,对 Zygote 进程的功能文件进行整理;

然后,整理 Zygote 进程在启动中的时序图。

接着从功能的角度对 Zygote 进程的运行进行整理,重要的部分详细描述

最后,总结 Zygote 进程的知识点。

在上一篇介绍 init 进程的文档中,我们提到在 init 的第二阶段会扫描系统路径下的 rc 文件,并解析执行所有的 rc 文件中定义功能。在 Android 中,很多的服务是在这个阶段被启动,作为系统的 Native 服务或者说是守护进程运行,在这些服务中,对于启动流程而言,必须要提到的一个服务进程就是 Zygote 进程。

1、rc 文件

Zygote 进程在 init 进程中以 service 的方式启动的。从 Android 5.0 开始,Zygote 还是有变动的,之前是直接放入 init.rc 中的代码块中,现在是放到了单独的文件中,通过 init.rc 中通过 “import” 的方式引入文件。

如下所示:

请添加图片描述

从上面的语句可以看到,init.rc 并不是直接引入某个固定的文件,而是根据属性 ro.zygote 的内容来引入不同的文件。这是因为从 Android 5.0 开始,Android 系统开始支持 64 位的编译,Zygote 进程本身也会有 32 位和 64 位版本的区别,因此,这里通过 ro.zygote 属性来控制启动不同版本的 Zyogte 进程。

ro.zygote 属性的取值可能有以下情况:
	zygote32
	zygote64
	zygote32_64
	zygote64_32

在系统路径 /system/core/rootdir 同级目录下,对应四个关于 zygote 的 rc 文件匹配 zygote 的启动。

/android/system/core/rootdir$ ls -l
-rw-r--r-- 1 domain users   959 Nov  9  2022 init.zygote32_64.rc
-rw-r--r-- 1 domain users   563 Nov  9  2022 init.zygote32.rc
-rw-r--r-- 1 domain users   981 Nov  9  2022 init.zygote64_32.rc
-rw-r--r-- 1 domain users   565 Nov  9  2022 init.zygote64.rc

那么现在我们的版本中对于 ro.zygote 属性值是如何定义的呢?如下所示:

项目:/ $ getprop | grep -i "ro.zygote"
[ro.zygote]: [zygote64_32]

现在的项目中使用的是 ro.zygote = zygote64_32,也就是对应使用 init.zygote64_32.rc 文件启动 Zygote 进程。

这四个 rc 文件对应了 Android 中支持的四种运行模式。

init.zygote32.rc : 纯32位模式
init.zygote32_64.rc : 混32位模式,即32位为主,64位为辅
init.zygote64.rc : 纯64位模式
init.zygote64_32.rc : 混64位模式,即64位为主,32位为辅

1.1 init.zygote32.rc

下面是 init.zygote32.rc 文件内容,系统使用此 rc 文件,zygote 将会以 32 位模式运行。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

1.2 init.zygote32_64.rc

下面是 init.zygote32_64.rc 文件文件内容,系统使用此 rc 文件,会启动两个 zygote 进程,32 位和 64 位两种服务,32 位为主,64 位为辅。

service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

service zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    socket usap_pool_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks

1.3 init.zygote64.rc

下面是 init.zygote64.rc 文件内容,系统使用此 rc 文件,zygote 将会运行在 64 位模式。

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

1.4 init.zygote64_32.rc

下面是 init.zygote64_32.rc 文件内容,系统使用此 rc 文件,将会启动两个zygote 进程,64 位和 32 位,64 位为主,32 位为辅。

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    socket usap_pool_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks

项目上现在使用的 rc 文件是 init.zygote64_32.rc 文件,我们来简单分析下文件内容

从 rc 文件中我们可以看出,定义了两个 zygote 服务:zygote 和 zygote_secondary。

zygote 是作为主服务,用于处理 64 位的程序,对应启动的可执行文件是 app_process64;

  • 启动的参数是“-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote”;

  • 定义了名为 zygote 的 Socket;

  • onrestart 指的是当 zygote 进程重启时,执行后面的指令,比如写入电源状态、重启系统服务等;

  • 当创建子进程时,在 /dev/cpuset/foreground/tasks 里写入 pid。

zygote_secondary 是作为辅服务,用于处理 32 位的程序,对应启动的可执行文件是 app_process32;

  • 启动的参数是“-Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload”;

  • 定义了名为 zygote_secondary 的 Socket;

  • 当 zygote_secondary 进程重启时,重启 zygote 服务;

  • 当创建子进程时,在 /dev/cpuset/foreground/tasks 下写入 pid。

2、app_process

app_process 是启动 zygote 时执行的程序。app_process 是 Android 上一个原生程序,是 APP 进程的主入口点,不仅是说可以启动 zygote 进程,还可以启动任何其他的 APP,简单理解为是一个可以使用虚拟机运行 main 函数的程序。

2.1 代码路径

app_process 的代码路径位于 /android/frameworks/base/cmds/app_process 路径下。文件结构如下:

ubuntu16-017:~/workspace/src/android/frameworks/base/cmds/app_process$ ls -l
total 28
-rw-r--r-- 1 domain users  1992 Nov  9  2022 Android.mk
-rw-r--r-- 1 domain users 12019 Nov  9  2022 app_main.cpp
-rw-r--r-- 1 domain users     0 Nov  9  2022 MODULE_LICENSE_APACHE2
-rw-r--r-- 1 domain users 10695 Nov  9  2022 NOTICE

2.2 编译

编译文件是 /android/frameworks/base/cmds/app_process/Android.mk,下面我们来看下编译

LOCAL_PATH:= $(call my-dir)

// 编译所需依赖库
app_process_common_shared_libs := \
    libandroid_runtime \
    libbinder \
    libcutils \
    libdl \
    libhidlbase \
    liblog \
    libnativeloader \
    libutils \

# This is a list of libraries that need to be included in order to avoid
# bad apps. This prevents a library from having a mismatch when resolving
# new/delete from an app shared library.
# See b/21032018 for more details.
app_process_common_shared_libs += \
    libwilhelm \

app_process_common_static_libs := \
    libsigchain \

// 编译的文件
app_process_src_files := \
    app_main.cpp \

app_process_cflags := \
    -Wall -Werror -Wunused -Wunreachable-code

app_process_ldflags_32 := \
    -Wl,--version-script,art/sigchainlib/version-script32.txt -Wl,--export-dynamic
app_process_ldflags_64 := \
    -Wl,--version-script,art/sigchainlib/version-script64.txt -Wl,--export-dynamic

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= $(app_process_src_files)

LOCAL_LDFLAGS_32 := $(app_process_ldflags_32)
LOCAL_LDFLAGS_64 := $(app_process_ldflags_64)

LOCAL_SHARED_LIBRARIES := $(app_process_common_shared_libs)

LOCAL_WHOLE_STATIC_LIBRARIES := $(app_process_common_static_libs)

// 编译模块名为 app_process
LOCAL_MODULE:= app_process
/*
* LOCAL_MULTILIB 可以指定模块编译 32 位或者 64 位或者都编译
* 值可选
* “both”: build both 32-bit and 64-bit.
* “32”: build only 32-bit.
* “64”: build only 64-bit.
* “first”: build for only the first arch (32-bit in 32-bit devices and 64-bit in 64-bit devices).
* “”: the default; the build system decides what arch to build based on the module class and other LOCAL_ variables, such as LOCAL_MODULE_TARGET_ARCH, LOCAL_32_BIT_ONLY, etc.
*/
LOCAL_MULTILIB := both
// 32 位可执行文件名为 app_process32
LOCAL_MODULE_STEM_32 := app_process32
// 64 位可执行文件名为 app_process64
LOCAL_MODULE_STEM_64 := app_process64

LOCAL_CFLAGS += $(app_process_cflags)

# In SANITIZE_LITE mode, we create the sanitized binary in a separate location (but reuse
# the same module). Using the same module also works around an issue with make: binaries
# that depend on sanitized libraries will be relinked, even if they set LOCAL_SANITIZE := never.
#
# Also pull in the asanwrapper helper.
ifeq ($(SANITIZE_LITE),true)
LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)/asan
LOCAL_REQUIRED_MODULES := asanwrapper
endif

// 编译可执行文件
include $(BUILD_EXECUTABLE)

# Create a symlink from app_process to app_process32 or 64
# depending on the target configuration.
ifneq ($(SANITIZE_LITE),true)
include  $(BUILD_SYSTEM)/executable_prefer_symlink.mk
endif

2.3 使用介绍

从参数介绍到进程的启动,创建虚拟机,通过反射启动。

用法如下:

app_process [vm-options] [工作目录] [options] 类名 [类的main方法参数] [类的main方法参数] ....

参数如下:

vm-options – VM 选项
work-dir –工作目录(如/system/bin,/data/app,/data/...) 
options –运行的参数 :
    –-zygote 启动 zygote 进程用的
    –-start-system-server
    –-application (api>=14) 启动应用程序
    –-nice-name=nice_proc_name (api>=14) (只有非--zygote模式下该选项才会生效)
[启动类名] –包含main方法的主类  (如com.android.internal.os.WrapperInit)
main-options –启动时候传递到main方法中的参数

到这里我们简单来概述一下,app_process 是用来启动程序的工具,可以启动 zygote 这样的系统服务,也可以启动 apk 这样的应用进程,也可以执行 java 程序。这里根据传入的参数,可以分为 zygote 模式和 非 zygote 模式。有以下注意点:

  • 传入 –zygote 会启动 com.android.internal.os.ZygoteInit,否则启动 com.android.internal.os.RuntimeInit。
  • –start-system-server 只在启动 zygote 时有效。
  • 在非 zygote 模式中,有无 –application 的选项的区别只是是否将 stdout 和 stderr 重定向到 AndroidPrintStream。
  • 只有在非 zygote 的情况下,–nice-name= 选项有效。

示例:

1、启动 zygote 进程命令:
app_process -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote

2、启动 com.android.test 应用命令:
app_process -Djava.class.path=/sdcard/classes.dex  /data/local/tmp --application --nice-name=helloservice com.android.test.HelloWorld 1 2 a

2、启动 com.android.test 应用命令(通过 apk 的方式)
app_process -Djava.class.path=/sdcard/app.apk /data/local com.android.test.HelloWorld

2.4 代码分析

2.4.1 app_main

app_process 程序的执行文件是 app_main.cpp 文件,入口是 main() 函数。文件路径为:/android/frameworks/base/cmds/app_process/app_main.cpp。

#if defined(__LP64__)
// 如果为 64 位进程,则进程名为 "zygote64",否则为 "zygote"
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64";
static const char ZYGOTE_NICE_NAME[] = "zygote64";
#else
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32";
static const char ZYGOTE_NICE_NAME[] = "zygote";
#endif

-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------

int main(int argc, char* const argv[])
{

-----------------------------------------------------------------------------------------
1、创建 AppRuntime 对象,AppRuntime 类用于创建和初始化虚拟机。
    if (!LOG_NDEBUG) {
      String8 argv_String;
      for (int i = 0; i < argc; ++i) {
        argv_String.append("\"");
        argv_String.append(argv[i]);
        argv_String.append("\" ");
      }
      ALOGV("app_process main with argv: %s", argv_String.string());
    }

    // 创建 AppRuntime 对象
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments argv[0] = "/system/bin/app_process"
    // ignore argv[0] 跳过 argv[0] 参数
    argc--;
    argv++;

-----------------------------------------------------------------------------------------
2、从给定参数中解析 Java-Option 参数,并把参数添加到 AppRuntime 中
    // Everything up to '--' or first non '-' arg goes to the vm.
    // 直到遇到 '-' 或第一个非 '-' 的参数为止的所有内容都将提供给虚拟机作为 options。
    // The first argument after the VM args is the "parent dir", which
    // is currently unused.
    //
    // After the parent dir, we expect one or more the following internal
    // arguments :
    //
    // --zygote : Start in zygote mode 启动到 zygote 模式
    // --start-system-server : Start the system server. 启动 system server
    // --application : Start in application (stand alone, non zygote) mode. 以应用程序模式启动 (独立启动, 非 zygote)
    // --nice-name : The nice name for this process. 给进程起一个名字
    //
    // 对于非 zygote 启动,这些参数后面将是主类名,所有其余的参数都传递给此类的 main 方法;
    // For non zygote starts, these arguments will be followed by
    // the main class name. All remaining arguments are passed to
    // the main method of this class.
    // 
    // 对于 zygote 启动,所有剩余的参数都传递给 zygote 的 main 方法。
    // For zygote starts, all remaining arguments are passed to the zygote.
    // main function.
    //
    // Note that we must copy argument string values since we will rewrite the
    // entire argument block when we apply the nice name to argv0.
    //
    // As an exception to the above rule, anything in "spaced commands"
    // goes to the vm even though it has a space in it.
    const char* spaced_commands[] = { "-cp", "-classpath" };
    // Allow "spaced commands" to be succeeded by exactly 1 argument (regardless of -s).
    bool known_command = false;

    int i;
    for (i = 0; i < argc; i++) {
        if (known_command == true) {
          runtime.addOption(strdup(argv[i]));
          // The static analyzer gets upset that we don't ever free the above
          // string. Since the allocation is from main, leaking it doesn't seem
          // problematic. NOLINTNEXTLINE
          ALOGV("app_process main add known option '%s'", argv[i]);
          known_command = false;
          continue;
        }

        for (int j = 0;
             j < static_cast<int>(sizeof(spaced_commands) / sizeof(spaced_commands[0]));
             ++j) {
          if (strcmp(argv[i], spaced_commands[j]) == 0) {
            known_command = true;
            ALOGV("app_process main found known command '%s'", argv[i]);
          }
        }

        if (argv[i][0] != '-') {
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            ++i; // Skip --.
            break;
        }

        // 将参数添加到 AppRuntime 中
        runtime.addOption(strdup(argv[i]));
        // The static analyzer gets upset that we don't ever free the above
        // string. Since the allocation is from main, leaking it doesn't seem
        // problematic. NOLINTNEXTLINE
        ALOGV("app_process main add option '%s'", argv[i]);
    }

-----------------------------------------------------------------------------------------
  这里继续解析参数,将信息保存在 zygote、startSystemServer、application 等变量中。
    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    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.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }

-----------------------------------------------------------------------------------------
3、准备 ZygoteInit 或 RuntimeInit 启动所需要的参数

    Vector<String8> args;
    // 没有处于 zygote 模式
    if (!className.isEmpty()) {
        // We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        //
        // 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);

        if (!LOG_NDEBUG) {
          String8 restOfArgs;
          char* const* argv_new = argv + i;
          int argc_new = argc - i;
          for (int k = 0; k < argc_new; ++k) {
            restOfArgs.append("\"");
            restOfArgs.append(argv_new[k]);
            restOfArgs.append("\" ");
          }
          ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string());
        }
    } else {
        // We're in zygote mode.
        
        // className 为空,处于 zygote 模式
        // 创建 /data/dalvik-cache/ 目录
        maybeCreateDalvikCache();

        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }

        char prop[PROP_VALUE_MAX];
        if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
            LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                ABI_LIST_PROPERTY);
            return 11;
        }

        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        // 获取支持的 abi 列表
        args.add(abiFlag);

        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        // 在 zygote 模式下,将所有剩余参数传递给 zygote 的 main() 方法。
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }

-----------------------------------------------------------------------------------------
4、设置进程名称
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

-----------------------------------------------------------------------------------------
5、启动 ZygoteInit 或者 RuntimeInit 程序。
    if (zygote) {
        // 启动 zygote 服务
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        // 启动 application 服务
        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 程序的功能:

  • 初始化 AppRuntime 对象
  • 从命令行中解析参数,并把参数都添加到 AppRuntime 中
  • 判断启动功能,zygote、application,并且准备对应启动参数
  • 设置进程名称
  • 调用 AppRuntime.start 函数启动 zygote 或者 application
2.4.2 AndroidRuntime/AppRuntime

AppRuntime 直译的话叫做应用运行时,开发时,我们写好 Java/Kotlin 代码,通过对应的编译器将代码编译为字节码,AppRuntime 的作用就是创建一个可以执行字节码的环境,这个环境主要由两部分内容构成:

  • 一部分负责对象的创建与回收,譬如类的加载器,垃圾回收器等
  • 一部分负责程序逻辑的运行,譬如即时编译系统,解释器等

AppRuntime 类定义在 app_main.cpp 中,继承了 AndroidRuntime 类,app_process 启动程序是会初始化一个 AndroidRuntime 对象,下面是 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 调用 AppRuntime.start 函数会调用到 AndroidRuntime 中,AppRuntime 是在 app_main.cpp 文件中定义的 AndroidRuntime 的子类。

AndroidRuntime 的代码在 /android/frameworks/base/core/jni/AndroidRuntime.cpp 文件中,下面来看下 start 函数的功能

// AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
  // 打印启动服务日志
  ALOGD(">>>>>> START %s uid %d <<<<<<\n",
          className != NULL ? className : "(unknown)", getuid());

  static const String8 startSystemServer("start-system-server");

  for (size_t i = 0; i < options.size(); ++i) {
    if (options[i] == startSystemServer) {
      const int LOG_BOOT_PROGRESS_START = 3000;
      LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
    }
  }

  // 获取 system 启动目录
  // 系统目录从环境变量 ANDROID_ROOT 中读取。如果说取失败,则默认设置目录为"/system"。如果连"/system"也没有,则 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);
  }

  // 通过 jni_invocation.Init(NULL) 完成 jni 接口的初始化
  JniInvocation jni_invocation;
  jni_invocation.Init(NULL);
  JNIEnv* env;
  // 创建虚拟机
  if (startVm(&mJavaVM, &env, zygote) != 0) {
    return;
  }
    
  // onVimCreate() 是一个虚函数,调用它实际上调用的是继承类的 AppRuntime 中的重载函数。
  onVmCreated(env);
    
  // 注册系统类 JNI 方法
  // startReg() 函数通过调用 register_jni_procs() 函数将全局的 gRegJNI 中的本地 JNI 函数在虚拟机中注册
  if (startReg(env) < 0) {
    ALOGE("Unable to register all android natives\n");
    return;
  }
  
  // 为启动 Java 类的 main 函数做准备
  jclass stringClass;
  jobjectArray strArray;
  jstring classNameStr;
  
  stringClass = env->FindClass("java/lang/String");
  assert(stringClass != NULL);
  // Java: strArray = new String[options.size() + 1];
  strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
  assert(strArray != NULL);
  classNameStr = env->NewStringUTF(className);
  assert(classNameStr != NULL);
  // Java: strArray[0] = "com.android.internal.os.ZygoteInit";
  env->SetObjectArrayElement(strArray, 0, classNameStr);
  
  // 将相关参数收集至 options 中,下面会传递给 ZygoteInit
  // --start-system-server, --abi-list=arm64-v8a ...
  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);
  }
  
  // 转换为 JNI 格式类名:com/android/internal/os/ZygoteInit
  char* slashClassName = toSlashClassName(className);
  jclass startClass = env->FindClass(slashClassName);
  if (startClass == NULL) {
    ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
  } else {
    // 通过 GetStaticMethodID 函数来获取 main() 方法的 id
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
       "([Ljava/lang/String;)V");
    if (startMeth == NULL) {
      ALOGE("JavaVM unable to find main() in '%s'\n", className);
    } else {
      // 调用 ZygoteInit.main() 调用 Java 层方法;
      env->CallStaticVoidMethod(startClass, startMeth, strArray);
    }
  }
  free(slashClassName);
  
  ALOGD("Shutting down VM\n");
  // 关闭 Java 虚拟机
  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");
}

AndroidRuntime.start() 函数的功能如下:

  1. 创建了一个 JniInvocation 的实例,并且调用它的成员函数 init 来初始化 JNI 环境。
  2. startVm 函数创建虚拟机及对应的 JNI 接口,即 JavaVM 接口和 JNIEnv 接口。startVm 函数中主要是 设置虚拟机配置参数 以及 创建虚拟机实例,创建虚拟机后每一个进程应该具有一个 JavaVM 指针,而每一个线程都具有一个 JNIEnv 指针。
  3. startReg 函数注册系统 JNI 方法;
  4. 收集 options 参数,加载指定的 class
JavaVM(Java Virtual Machine)和 JNIEnv(Java Native Interface Environment)是 Java Native Interface(JNI)中两个重要的概念,它们在实现 Java 与本地代码之间的交互时起着重要的作用。

JavaVM:JavaVM 是一个代表 Java 虚拟机的结构体指针,它提供了一系列 JNI 函数,可以用于创建 Java 虚拟机、获取当前 Java 虚拟机等操作。JavaVM 提供了一种机制,可以在本地方法中获取对 Java 虚拟机的访问,从而可以在本地代码中操作 Java 对象、调用 Java 方法等功能。JavaVM 的作用是为本地代码提供对 Java 虚拟机的操作接口,使得本地代码能够与 Java 虚拟机进行交互。

JNIEnv:JNIEnv 是一个代表 JNI 环境的指针类型,它是通过 JavaVM 获取到的,每个线程都会有一个对应的 JNIEnv。JNIEnv 提供了一系列 JNI 函数,可以用于在本地代码中访问 Java 对象、调用 Java 方法、处理异常等操作。通过 JNIEnv,本地代码可以与 Java 代码进行通信,操作 Java 对象,并实现 Java 与本地代码之间的数据传递和交互。

总的来说,JavaVM 提供了对 Java 虚拟机的操作接口,而 JNIEnv 则提供了 JNI 环境的相关操作接口,这两者协同工作,使得本地代码能够与 Java 代码进行交互,实现 Java 与本地代码的互通。
时序图

这里考虑把时序图放在前面,总结描述。

这里可以画一张 app_main 和 AndroidRuntime 的初始化和交互逻辑图,包括再到 JNI 并通过反射找到对应函数的时序。

请添加图片描述

3、zygote 启动

zygote 启动时传入的参数是:com.android.internal.os.ZygoteInit。那么在通过 app_process 程序启动时,最终会通过反射找到 ZygoteInit 文件作为 Zygote 的入口文件。

本节将会从 ZygoteInit 文件开始,描述 Zygote 在启动过程中提供的功能。

3.1 时序图

时序图如下所示:

请添加图片描述

时序图简要描述:

从时序图中我们可以大致整理 Zygote 进程在 Android 初始化过程中提供的功能。

首先第一点,预加载资源。Android 中有很多资源是可以共享的,在启动阶段进行预加载可以在后续的进程中共享资源,也就避免了后续再去加载的动作,这样对于后续进程的启动、运行有很大的提升。这里的预加载并没有一次性完成,而是在线程中分步完成。这里是一次修改变更,由原来在一个函数中完成资源的预加载变更为开启不同的子线程去加载不同的资源,等待子线程完成预加载之后,zygote 进程再去执行后续的动作。

第二点,初始化 ZygoteServer 类。ZygoteServer 会去创建 Zygote 所需的 Socket 对象,用于后续的 socket 通信。

第三点,fork SystemServer 进程。启动 SystemServer 服务。

第四点,开启循环监听 Socket 通信。

3.2 Zygote 启动流程

前面提到在 app_process 会找到 ZygoteInit 作为 Zygote 的入口文件,执行 main() 函数。

ZygoteInit 文件路径为:/android/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

public static void main(String argv[]) {
    
-----------------------------------------------------------------------------------------
1、设置进程 group id;记录时间和 Trace 信息;启动 DDMS 虚拟机监控服务
    
    ZygoteServer zygoteServer = null;

    // Mark zygote start. This ensures that thread creation will throw
    // an error.
    // 调用此方法后,虚拟机会拒绝线程的创建,如果此时创建线程会产生异常
    ZygoteHooks.startZygoteNoThreadCreation();

    // Zygote goes into its own process group.
    // 设置 zygote 进程的 group id
    try {
        Os.setpgid(0, 0);
    } catch (ErrnoException ex) {
        throw new RuntimeException("Failed to setpgid(0,0)", ex);
    }

    Runnable caller;
    try {
        // Report Zygote start time to tron unless it is a runtime restart
        // 记录一些时间信息,用于性能分析
        if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
            MetricsLogger.histogram(null, "boot_zygote_init",
                    (int) SystemClock.elapsedRealtime());
        }

        // 记录 trace 信息
        String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
        TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
                Trace.TRACE_TAG_DALVIK);
        bootTimingsTraceLog.traceBegin("ZygoteInit");
        // 启动 DDMS 虚拟机监控调试服务
        RuntimeInit.enableDdms();

-----------------------------------------------------------------------------------------
2、解析参数;预加载资源
    
        boolean startSystemServer = false;
        String zygoteSocketName = "zygote";
        String abiList = null;
        boolean enableLazyPreload = false;
        for (int i = 1; i < argv.length; i++) {
            if ("start-system-server".equals(argv[i])) {
                // 判断是否开启 SystemServer
                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)) {
                // 获取 zygote socket 的名字
                zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());
            } else {
                throw new RuntimeException("Unknown command line argument: " + argv[i]);
            }
        }

        // 通过 zygote socket 的名字判断是否是主 zygote。
        final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);

        // 没有指定 abi 会有异常
        if (abiList == null) {
            throw new RuntimeException("No ABI list supplied.");
        }

        // 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());
            //multithread to do preload start
            //preload(bootTimingsTraceLog);
            // 预加载资源
            partialPreload(bootTimingsTraceLog);
            //multithread to do preload end
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                    SystemClock.uptimeMillis());
            bootTimingsTraceLog.traceEnd(); // ZygotePreload
        } else {
            Zygote.resetNicePriority();
        }

        bootTimingsTraceLog.traceEnd(); // ZygoteInit
        // Disable tracing so that forked processes do not inherit stale tracing tags from
        // Zygote.
        Trace.setTracingEnabled(false, 0);


-----------------------------------------------------------------------------------------
3、初始化 ZygoteServer 服务类,用于注册 socket 监听;创建 SystemServer 进程;
        // 执行一些初始化操作
        Zygote.initNativeState(isPrimaryZygote);

        // 与 startZygoteNoThreadCreation 函数对应,虚拟机可以创建线程了
        ZygoteHooks.stopZygoteNoThreadCreation();

        // 初始化 ZygoteServer 进程,管理 socket 注册监听
        zygoteServer = new ZygoteServer(isPrimaryZygote);

        if (startSystemServer) {
            // fork SystemServer 进程
            Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
            // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
            // child (system_server) process.
            // 如果 r 为空,说明是父进程 zygote,无任何处理,继续执行
            // 如果 r 不为空,说明是子进程 SystemServer,启动后直接返回
            if (r != null) {
                r.run();
                return;
            }
        }

-----------------------------------------------------------------------------------------
4、在线程中预加载资源,包括 ClassResourcesSharedLibraries、openGL 资源等。开启一个无限循环,处理 socket 信息;
        Log.i(TAG, "Accepting command socket connections");
        //multithread to do preload start
        if (!enableLazyPreload) {
            Thread loadClassThread = new Thread(new Runnable() {
                public void run() {
                    // 在线程中预加载 Classes 资源
                    preloadClasses();
                }
            });
            loadClassThread.start();

            Thread loadResourceThread = new Thread(new Runnable() {
                public void run() {
                    // 在线程中预加载 Resources 资源
                    preloadResources();
                }
            });
            loadResourceThread.start();

            Thread loadLibraryThread = new Thread(new Runnable() {
                public void run() {
                    // 在线程中预加载 SharedLibraries 资源
                    preloadSharedLibraries();
                    // 在线程中预加载 openGL 资源
                    maybePreloadGraphicsDriver();
                }
            });
            loadLibraryThread.start();

            try {
                loadClassThread.join();
                loadResourceThread.join();
                loadLibraryThread.join();

                // Do an initial gc to clean up after startup
                bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
                gcAndFinalize();
                bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
            } catch(Exception e){}
        }
        //multithread to do preload end
        // The select loop returns early in the child process after a fork and
        // loops forever in the zygote.
        // 调用 ZygoteServer 开启一个无限循环,处理 socket 信息
        caller = zygoteServer.runSelectLoop(abiList);
    } catch (Throwable ex) {
        Log.e(TAG, "System zygote died with exception", ex);
        throw ex;
    } finally {
        if (zygoteServer != null) {
            zygoteServer.closeServerSocket();
        }
    }

    // We're in the child process and have exited the select loop. Proceed to execute the
    // command.
    // 执行新进程的主函数
    if (caller != null) {
        caller.run();
    }
}

说明点:

1、预加载资源由原来的全部加载变为线程中加载,可以提高运行效率,减少开机时间。控制参数是 --enable-lazy-preload,仅有在 init.zygote64_32.rc 中的 zygote64 中有此参数,说明在现有代码逻辑中,仅有 zygote64 才会在启动时预加载资源。

首先第一点,预加载资源。Android 中有很多资源是可以共享的,在启动阶段进行预加载可以在后续的进程中共享资源,也就避免了后续再去加载的动作,这样对于后续进程的启动、运行有很大的提升。这里的预加载并没有一次性完成,而是在线程中分步完成。这里是一次修改变更,由原来在一个函数中完成资源的预加载变更为开启不同的子线程去加载不同的资源,等待子线程完成预加载之后,zygote 进程再去执行后续的动作。

第二点,初始化 ZygoteServer 类。ZygoteServer 会去创建 Zygote 所需的 Socket 对象,用于后续的 socket 通信。

第三点,fork SystemServer 进程。启动 SystemServer 服务。

第四点,开启循环监听 Socket 通信。

3.2.1 预加载资源

预加载资源主要是进行一些 类、资源、共享库的预加载工作,以提升运行时效率。封装的函数包括 preloadClasses()、preloadResources()、preloadSharedLibraries() 等,我们来简单看一下对于这些资源是如何加载的。

    private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();

        InputStream is;
        try {
            // /system/etc/preloaded-classes
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }

        Log.i(TAG, "Preloading classes...");
        long startTime = SystemClock.uptimeMillis();

        // Drop root perms while running static initializers.
        final int reuid = Os.getuid();
        final int regid = Os.getgid();

        // We need to drop root perms only if we're already root. In the case of "wrapped"
        // processes (see WrapperInit), this function is called from an unprivileged uid
        // and gid.
        boolean droppedPriviliges = false;
        if (reuid == ROOT_UID && regid == ROOT_GID) {
            try {
                Os.setregid(ROOT_GID, UNPRIVILEGED_GID);
                Os.setreuid(ROOT_UID, UNPRIVILEGED_UID);
            } catch (ErrnoException ex) {
                throw new RuntimeException("Failed to drop root", ex);
            }

            droppedPriviliges = true;
        }

        // Alter the target heap utilization.  With explicit GCs this
        // is not likely to have any effect.
        float defaultUtilization = runtime.getTargetHeapUtilization();
        runtime.setTargetHeapUtilization(0.8f);

        try {
            BufferedReader br =
                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {
                // Skip comments and blank lines.
                line = line.trim();
                if (line.startsWith("#") || line.equals("")) {
                    continue;
                }

                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                try {
                    if (false) {
                        Log.v(TAG, "Preloading " + line + "...");
                    }
                    // Load and explicitly initialize the given class. Use
                    // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
                    // (to derive the caller's class-loader). Use true to force initialization, and
                    // null for the boot classpath class-loader (could as well cache the
                    // class-loader of this class in a variable).
                    Class.forName(line, true, null);
                    count++;
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } catch (UnsatisfiedLinkError e) {
                    Log.w(TAG, "Problem preloading " + line + ": " + e);
                } catch (Throwable t) {
                    Log.e(TAG, "Error preloading " + line + ".", t);
                    if (t instanceof Error) {
                        throw (Error) t;
                    }
                    if (t instanceof RuntimeException) {
                        throw (RuntimeException) t;
                    }
                    throw new RuntimeException(t);
                }
                Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
            }

            Log.i(TAG, "...preloaded " + count + " classes in "
                    + (SystemClock.uptimeMillis() - startTime) + "ms.");
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
        } finally {
            IoUtils.closeQuietly(is);
            // Restore default.
            runtime.setTargetHeapUtilization(defaultUtilization);

            // Fill in dex caches with classes, fields, and methods brought in by preloading.
            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadDexCaches");
            runtime.preloadDexCaches();
            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);

            // Bring back root. We'll need it later if we're in the zygote.
            if (droppedPriviliges) {
                try {
                    Os.setreuid(ROOT_UID, ROOT_UID);
                    Os.setregid(ROOT_GID, ROOT_GID);
                } catch (ErrnoException ex) {
                    throw new RuntimeException("Failed to restore root", ex);
                }
            }
        }
    }

上面是加载 类 资源,加载逻辑是读取 /system/etc/preloaded-classes 文件,并通过 Class.forName() 方法逐行加载文件中声明的类。提前预加载系统常用的类可以提升运行效率,但是这个预加载的过程耗时等资源消耗比较多。在源码目录 /android/frameworks/base/config 下,存在 preloaded-classes 文件,存放着启动启动时需要加载的常用类。下面是部分资源加载内容:

android.app.Activity
android.app.ActivityManager$1
android.app.ActivityManager$AppTask
android.app.ActivityManager$MemoryInfo$1
android.app.ActivityManager$MemoryInfo
android.app.ActivityManager$OnUidImportanceListener
android.app.ActivityManager$RecentTaskInfo$1
android.app.ActivityManager$RecentTaskInfo
android.app.ActivityManager$RunningAppProcessInfo$1
android.app.ActivityManager$RunningAppProcessInfo
android.app.ActivityManager$RunningServiceInfo$1
android.app.ActivityManager$RunningServiceInfo
android.app.ActivityManager$RunningTaskInfo$1
android.app.ActivityManager$RunningTaskInfo
android.app.ActivityManager$TaskDescription$1
android.app.ActivityManager$TaskDescription
android.app.ActivityManager$UidObserver
android.app.ActivityManager

下面是预加载资源 preloadResources() 函数:

private static void preloadResources() {
        final VMRuntime runtime = VMRuntime.getRuntime();

        try {
            mResources = Resources.getSystem();
            mResources.startPreloading();
            if (PRELOAD_RESOURCES) {
                Log.i(TAG, "Preloading resources...");

                long startTime = SystemClock.uptimeMillis();
                TypedArray ar = mResources.obtainTypedArray(
                        com.android.internal.R.array.preloaded_drawables);
                int N = preloadDrawables(ar);
                ar.recycle();
                Log.i(TAG, "...preloaded " + N + " resources in "
                        + (SystemClock.uptimeMillis() - startTime) + "ms.");

                startTime = SystemClock.uptimeMillis();
                ar = mResources.obtainTypedArray(
                        com.android.internal.R.array.preloaded_color_state_lists);
                N = preloadColorStateLists(ar);
                ar.recycle();
                Log.i(TAG, "...preloaded " + N + " resources in "
                        + (SystemClock.uptimeMillis() - startTime) + "ms.");

                if (mResources.getBoolean(
                        com.android.internal.R.bool.config_freeformWindowManagement)) {
                    startTime = SystemClock.uptimeMillis();
                    ar = mResources.obtainTypedArray(
                            com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
                    N = preloadDrawables(ar);
                    ar.recycle();
                    Log.i(TAG, "...preloaded " + N + " resource in "
                            + (SystemClock.uptimeMillis() - startTime) + "ms.");
                }
            }
            mResources.finishPreloading();
        } catch (RuntimeException e) {
            Log.w(TAG, "Failure preloading resources", e);
        }
    }

预加载资源是定义在 /android/frameworks/base/core/res/res/values/arrays.xml 文件下的,预加载资源如下:

com.android.internal.R.array.preloaded_drawables
com.android.internal.R.array.preloaded_color_state_lists
com.android.internal.R.bool.config_freeformWindowManagement

下面是 /android/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>

<array name="preloaded_freeform_multi_window_drawables">
      <item>@drawable/decor_maximize_button_dark</item>
      <item>@drawable/decor_maximize_button_light</item>
</array>

下面是预加载共享库 preloadSharedLibraries() 函数:

private static void preloadSharedLibraries() {
        Log.i(TAG, "Preloading shared libraries...");
        System.loadLibrary("android");
        System.loadLibrary("compiler_rt");
        System.loadLibrary("jnigraphics");
    }

预加载共享库函数是预加载了三个共享库:libandroid.so、libcompiler_rt.so 和 libjnigraphics.so。

3.2.2 初始化 ZygoteServer

Zygote 通过 ZygoteServer 封装了对于 socket 通信的操作。

下面是 ZygoteServer 初始化函数:

    ZygoteServer(boolean isPrimaryZygote) {
        mUsapPoolEventFD = Zygote.getUsapPoolEventFD();

        // 这里通过 Zygote.createManagedSocketFromInitSocket 创建 LocalServerSocket 对象。
        if (isPrimaryZygote) {
            mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);
            mUsapPoolSocket =
                    Zygote.createManagedSocketFromInitSocket(
                            Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);
        } else {
            mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);
            mUsapPoolSocket =
                    Zygote.createManagedSocketFromInitSocket(
                            Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);
        }

        fetchUsapPoolPolicyProps();

        mUsapPoolSupported = true;
    }

ZygoteServer 函数创建 LocalServerSocket 对象来进行 Socket 通信。

static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
        int fileDesc;
        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;

        try {
            String env = System.getenv(fullSocketName);
            fileDesc = Integer.parseInt(env);
        } catch (RuntimeException ex) {
            throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
        }

        try {
            FileDescriptor fd = new FileDescriptor();
            fd.setInt$(fileDesc);
            return new LocalServerSocket(fd);
        } catch (IOException ex) {
            throw new RuntimeException(
                "Error building socket from file descriptor: " + fileDesc, ex);
        }
    }

下面是 Android Socket 的通信架构图:
请添加图片描述

LocalSocket 就是作为客户端建立于服务端的连接,发送数据。LocalServerSocket 作为服务端使用,建立服务端的 socket 监听客户端请求。

3.2.3 复制 SystemServer 进程

Zygote 进程在会复制 SystemServer 继续 Android 的启动。调用函数是 forkSystemServer 函数。

    private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
        long capabilities = posixCapabilitiesAsBits(
                OsConstants.CAP_IPC_LOCK,
                OsConstants.CAP_KILL,
                OsConstants.CAP_NET_ADMIN,
                OsConstants.CAP_NET_BIND_SERVICE,
                OsConstants.CAP_NET_BROADCAST,
                OsConstants.CAP_NET_RAW,
                OsConstants.CAP_SYS_MODULE,
                OsConstants.CAP_SYS_NICE,
                OsConstants.CAP_SYS_PTRACE,
                OsConstants.CAP_SYS_TIME,
                OsConstants.CAP_SYS_TTY_CONFIG,
                OsConstants.CAP_WAKE_ALARM,
                OsConstants.CAP_BLOCK_SUSPEND
        );
        /* Containers run without some capabilities, so drop any caps that are not available. */
        StructCapUserHeader header = new StructCapUserHeader(
                OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
        StructCapUserData[] data;
        try {
            data = Os.capget(header);
        } catch (ErrnoException ex) {
            throw new RuntimeException("Failed to capget()", ex);
        }
        capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32);

        // 启动参数
        /* Hardcoded command line to start the system server */
        String args[] = {
                "--setuid=1000",
                "--setgid=1000",
                "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
                "--capabilities=" + capabilities + "," + capabilities,
                "--nice-name=system_server",
                "--runtime-args",
                "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
                "com.android.server.SystemServer",
        };
        ZygoteArguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteArguments(args);
            Zygote.applyDebuggerSystemProperty(parsedArgs);
            Zygote.applyInvokeWithSystemProperty(parsedArgs);

            boolean profileSystemServer = SystemProperties.getBoolean(
                    "dalvik.vm.profilesystemserver", false);
            if (profileSystemServer) {
                parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
            }

            // fork SystemServer 进程
            /* Request to fork the system server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.mUid, parsedArgs.mGid,
                    parsedArgs.mGids,
                    parsedArgs.mRuntimeFlags,
                    null,
                    parsedArgs.mPermittedCapabilities,
                    parsedArgs.mEffectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        // pid = 0 表示子进程,从这里开始进入 SystemServer 进程。
        /* For child process */
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            // 关闭并释放从 Zygote copy 过来的 socket
            zygoteServer.closeServerSocket(); 
            // 完成新创建的 system_server 进程的剩余工作
            return handleSystemServerProcess(parsedArgs);
        }

       /**
       * 注意 fork() 函数式一次执行,两次返回(两个进程对同一程序的两次执行)。
       * pid > 0  说明还是父进程。pid = 0 说明进入了子进程
       * 所以这里的 return null 依旧会执行 
       */
        return null;
    }

从上面的启动参数可以看到,SystemServer 进程的 uid 和 gid 都是 1000,进程名是 system_server,其最后要加载的类名是 com.android.server.SystemServer。准备好一系列参数之后通过 ZygoteConnection.Arguments() 拼接,接着调用 Zygote.forkSystemServer() 方法真正的 fork 出子进程 system_server。

    public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
        ZygoteHooks.preFork();
        // Resets nice priority for zygote process.
        resetNicePriority();
        // 这里最终调用 nativeForkSystemServer 去执行 fork 操作。
        int pid = nativeForkSystemServer(
                uid, gid, gids, runtimeFlags, rlimits,
                permittedCapabilities, effectiveCapabilities);
        // Enable tracing as soon as we enter the system_server.
        if (pid == 0) {
            Trace.setTracingEnabled(true, runtimeFlags);
        }
        ZygoteHooks.postForkCommon();
        return pid;
    }

最后的 fork() 操作是在 native 层完成的。再回到 ZygoteInit.forkSystemServer() 中执行 fork() 之后的逻辑处理。对于 SystemServer 进程来说,fork 函数返回值是 0,会继续执行 handleSystemServerProcess() 函数,执行 SystemServer 的逻辑。对于 Zygote 进程,会启动下一步内容。

3.2.4 开启循环进行 Socket 通信

Zygote 进程在复制 SystemServer 进程后,SystemServer 继续执行启动逻辑,Zygote 进程会调用 zygoteServer.runSelectLoop() 函数开启循环进行 Socket 通信。

    Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        // mServerSocket 是之前在 Zygote 中创建的
        socketFDs.add(mZygoteSocket.getFileDescriptor());
        peers.add(null);

        while (true) {
            fetchUsapPoolPolicyPropsWithMinInterval();

            int[] usapPipeFDs = null;
            StructPollfd[] pollFDs = null;

            // Allocate enough space for the poll structs, taking into account
            // the state of the USAP pool for this Zygote (could be a
            // regular Zygote, a WebView Zygote, or an AppZygote).
            if (mUsapPoolEnabled) {
                usapPipeFDs = Zygote.getUsapPipeFDs();
                pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length];
            } else {
                pollFDs = new StructPollfd[socketFDs.size()];
            }

            /*
             * For reasons of correctness the USAP pool pipe and event FDs
             * must be processed before the session and server sockets.  This
             * is to ensure that the USAP pool accounting information is
             * accurate when handling other requests like API blacklist
             * exemptions.
             */

            int pollIndex = 0;
            for (FileDescriptor socketFD : socketFDs) {
                pollFDs[pollIndex] = new StructPollfd();
                pollFDs[pollIndex].fd = socketFD;
                pollFDs[pollIndex].events = (short) POLLIN;
                ++pollIndex;
            }

            final int usapPoolEventFDIndex = pollIndex;

            if (mUsapPoolEnabled) {
                pollFDs[pollIndex] = new StructPollfd();
                pollFDs[pollIndex].fd = mUsapPoolEventFD;
                pollFDs[pollIndex].events = (short) POLLIN;
                ++pollIndex;

                for (int usapPipeFD : usapPipeFDs) {
                    FileDescriptor managedFd = new FileDescriptor();
                    managedFd.setInt$(usapPipeFD);

                    pollFDs[pollIndex] = new StructPollfd();
                    pollFDs[pollIndex].fd = managedFd;
                    pollFDs[pollIndex].events = (short) POLLIN;
                    ++pollIndex;
                }
            }

            try {
                // 有事件来时往下执行,没有时就阻塞
                Os.poll(pollFDs, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }

            boolean usapPoolFDRead = false;

            while (--pollIndex >= 0) {
                if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                    continue;
                }

                if (pollIndex == 0) {
                    // 有新客户端连接
                    // Zygote server socket

                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    socketFDs.add(newPeer.getFileDescriptor());

                } else if (pollIndex < usapPoolEventFDIndex) {
                    // Session socket accepted from the Zygote server socket

                    try {
                         // 处理客户端请求
                        ZygoteConnection connection = peers.get(pollIndex);
                        final Runnable command = connection.processOneCommand(this);

                        // TODO (chriswailes): Is this extra check necessary?
                        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.
                            if (command != null) {
                                throw new IllegalStateException("command != null");
                            }

                            // We don't know whether the remote side of the socket was closed or
                            // not until we attempt to read from it from processOneCommand. This
                            // shows up as a regular POLLIN event in our regular processing loop.
                            if (connection.isClosedByPeer()) {
                                connection.closeSocket();
                                peers.remove(pollIndex);
                                socketFDs.remove(pollIndex);
                            }
                        }
                    } catch (Exception e) {
                        if (!mIsForkChild) {
                            // We're in the server so any exception here is one that has taken place
                            // pre-fork while processing commands or reading / writing from the
                            // control socket. Make a loud noise about any such exceptions so that
                            // we know exactly what failed and why.

                            Slog.e(TAG, "Exception executing zygote command: ", e);

                            // Make sure the socket is closed so that the other end knows
                            // immediately that something has gone wrong and doesn't time out
                            // waiting for a response.
                            ZygoteConnection conn = peers.remove(pollIndex);
                            conn.closeSocket();

                            socketFDs.remove(pollIndex);
                        } else {
                            // We're in the child so any exception caught here has happened post
                            // fork and before we execute ActivityThread.main (or any other main()
                            // method). Log the details of the exception and bring down the process.
                            Log.e(TAG, "Caught post-fork exception in child process.", e);
                            throw e;
                        }
                    } finally {
                        // Reset the child flag, in the event that the child process is a child-
                        // zygote. The flag will not be consulted this loop pass after the Runnable
                        // is returned.
                        mIsForkChild = false;
                    }
                } else {
                    // Either the USAP pool event FD or a USAP reporting pipe.

                    // If this is the event FD the payload will be the number of USAPs removed.
                    // If this is a reporting pipe FD the payload will be the PID of the USAP
                    // that was just specialized.
                    long messagePayload = -1;

                    try {
                        byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
                        int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);

                        if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
                            DataInputStream inputStream =
                                    new DataInputStream(new ByteArrayInputStream(buffer));

                            messagePayload = inputStream.readLong();
                        } else {
                            Log.e(TAG, "Incomplete read from USAP management FD of size "
                                    + readBytes);
                            continue;
                        }
                    } catch (Exception ex) {
                        if (pollIndex == usapPoolEventFDIndex) {
                            Log.e(TAG, "Failed to read from USAP pool event FD: "
                                    + ex.getMessage());
                        } else {
                            Log.e(TAG, "Failed to read from USAP reporting pipe: "
                                    + ex.getMessage());
                        }

                        continue;
                    }

                    if (pollIndex > usapPoolEventFDIndex) {
                        Zygote.removeUsapTableEntry((int) messagePayload);
                    }

                    usapPoolFDRead = true;
                }
            }

            // Check to see if the USAP pool needs to be refilled.
            if (usapPoolFDRead) {
                int[] sessionSocketRawFDs =
                        socketFDs.subList(1, socketFDs.size())
                                .stream()
                                .mapToInt(fd -> fd.getInt$())
                                .toArray();

                final Runnable command = fillUsapPool(sessionSocketRawFDs);

                if (command != null) {
                    return command;
                }
            }
        }
    }

mServerSocketZygoteInit.main() 中一开始就建立的服务端 socket,用于处理客户端请求。一看到 while(true) 就肯定会有阻塞操作。Os.poll() 在有事件来时往下执行,否则就阻塞。当有客户端请求过来时,调用 ZygoteConnection.processOneCommand() 方法来处理。

 Runnable processOneCommand(ZygoteServer zygoteServer) {
        String args[];
        ZygoteArguments parsedArgs = null;
        FileDescriptor[] descriptors;

        try {
            args = Zygote.readArgumentList(mSocketReader);

            // TODO (chriswailes): Remove this and add an assert.
            descriptors = mSocket.getAncillaryFileDescriptors();
        } catch (IOException ex) {
            throw new IllegalStateException("IOException on command socket", ex);
        }

        // readArgumentList returns null only when it has reached EOF with no available
        // data to read. This will only happen when the remote socket has disconnected.
        if (args == null) {
            isEof = true;
            return null;
        }

        int pid = -1;
        FileDescriptor childPipeFd = null;
        FileDescriptor serverPipeFd = null;

        parsedArgs = new ZygoteArguments(args);

        if (parsedArgs.mAbiListQuery) {
            handleAbiListQuery();
            return null;
        }

        if (parsedArgs.mPidQuery) {
            handlePidQuery();
            return null;
        }

        if (parsedArgs.mUsapPoolStatusSpecified) {
            return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
        }

        if (parsedArgs.mPreloadDefault) {
            handlePreload();
            return null;
        }

        if (parsedArgs.mPreloadPackage != null) {
            handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
                    parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
            return null;
        }

        if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
            byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
            Parcel appInfoParcel = Parcel.obtain();
            appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
            appInfoParcel.setDataPosition(0);
            ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
            appInfoParcel.recycle();
            if (appInfo != null) {
                handlePreloadApp(appInfo);
            } else {
                throw new IllegalArgumentException("Failed to deserialize --preload-app");
            }
            return null;
        }

        if (parsedArgs.mApiBlacklistExemptions != null) {
            return handleApiBlacklistExemptions(zygoteServer, parsedArgs.mApiBlacklistExemptions);
        }

        if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
                || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
            return handleHiddenApiAccessLogSampleRate(zygoteServer,
                    parsedArgs.mHiddenApiAccessLogSampleRate,
                    parsedArgs.mHiddenApiAccessStatslogSampleRate);
        }

        if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
            throw new ZygoteSecurityException("Client may not specify capabilities: "
                    + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
                    + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
        }

        Zygote.applyUidSecurityPolicy(parsedArgs, peer);
        Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);

        Zygote.applyDebuggerSystemProperty(parsedArgs);
        Zygote.applyInvokeWithSystemProperty(parsedArgs);

        int[][] rlimits = null;

        if (parsedArgs.mRLimits != null) {
            rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
        }

        int[] fdsToIgnore = null;

        if (parsedArgs.mInvokeWith != null) {
            try {
                FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
                childPipeFd = pipeFds[1];
                serverPipeFd = pipeFds[0];
                Os.fcntlInt(childPipeFd, F_SETFD, 0);
                fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
            } catch (ErrnoException errnoEx) {
                throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
            }
        }

        /**
         * In order to avoid leaking descriptors to the Zygote child,
         * the native code must close the two Zygote socket descriptors
         * in the child process before it switches from Zygote-root to
         * the UID and privileges of the application being launched.
         *
         * In order to avoid "bad file descriptor" errors when the
         * two LocalSocket objects are closed, the Posix file
         * descriptors are released via a dup2() call which closes
         * the socket and substitutes an open descriptor to /dev/null.
         */

        int [] fdsToClose = { -1, -1 };

        FileDescriptor fd = mSocket.getFileDescriptor();

        if (fd != null) {
            fdsToClose[0] = fd.getInt$();
        }

        fd = zygoteServer.getZygoteSocketFileDescriptor();

        if (fd != null) {
            fdsToClose[1] = fd.getInt$();
        }

        fd = null;

        // 这里调用 Zygote.forkAndSpecialize 去执行 fork 进程的逻辑。
        pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
                parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
                parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion);

        try {
            if (pid == 0) {
                // in child
                zygoteServer.setForkChild();

                zygoteServer.closeServerSocket();
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;

                return handleChildProc(parsedArgs, descriptors, childPipeFd,
                        parsedArgs.mStartChildZygote);
            } 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;
            }
        } finally {
            IoUtils.closeQuietly(childPipeFd);
            IoUtils.closeQuietly(serverPipeFd);
        }
    }

这里的进程复制跟 SystemServer 进程的复制类似,SystemServer 进程复制调用的是 Zygote.forkSystemServer 函数,Socket 通信时进程的复制调用的是 Zygote.forkAndSpecialize 函数,在 native 层的是实现逻辑类似。通过对应的 Socket 指令去执行对应的功能。

对于进程的复制,可以从下图中对 init 进程复制进程、zygote 进程复制进程的联系:
请添加图片描述

总结

本篇文档主要描述了 Android 启动流程中 Zygote 进程的内容。描述了从 Zygote 的启动,app_process 程序的启动以及 Zygote 初始化、预加载、进程的赋值等内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2228189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新AI软件部署,ChatGPT商业AI系统源码,支持GPT4.0+AI换脸+AI智能体GPTs应用+AI绘画+AI视频+文档分析

一、前言 SparkAi创作系统是一款基于ChatGPT和Midjourney开发的智能问答和绘画系统&#xff0c;提供一站式 AI B/C 端解决方案&#xff0c;AI大模型提问、AI绘画、专业版AI视频生成、文档分析、多模态识图理解、TTS & 语音识别对话、AI换脸、支持AI智能体应用&#xff08;…

C++ | Leetcode C++题解之第514题自由之路

题目&#xff1a; 题解&#xff1a; class Solution { public:int findRotateSteps(string ring, string key) {int n ring.size(), m key.size();vector<int> pos[26];for (int i 0; i < n; i) {pos[ring[i] - a].push_back(i);}vector<vector<int>>…

联想笔记本电脑睡眠后打开黑屏解决方法

下载联想机器睡眠无法唤醒修复工具 下载地址&#xff1a;https://tools.lenovo.com.cn/exeTools/detail/id/233/rid/6182522.html 使用完后重启电脑&#xff0c;问题解决。

应用案例 | Panorama SCADA助力巴黎奥运会:保障赛事协调与安全

谈到2024年最受关注的体育盛事&#xff0c;巴黎奥运会无疑是焦点之一。作为全球瞩目的顶级赛事&#xff0c;它不仅汇集了来自世界各地的精英运动员&#xff0c;还点燃了全球观众的热情。然而&#xff0c;组织如此大规模的活动绝非易事。从大量游客通过公共交通涌入&#xff0c;…

Flux-IP-Adapter-V2版本发布,效果实测!是惊喜还是意外?

更多AI教程&#xff1a;AI教程_深度学习入门指南 - 站长素材 简介 XLAB团队发布了FLUX.1-dev模型的最新IP-Adapter V2版本。这是在之前IP-Adapter V1版本上的进一步升级。新版本的IP-Adapter模型在保持图像纵横比的同时&#xff0c;分别在512x512分辨率下训练了150k步&#x…

【计算机网络教程】课程 章节测试1 计算机网络概述

一. 单选题&#xff08;共16题&#xff09; 1 【单选题】以下关于TCP/IP参考模型缺点的描述中&#xff0c;错误的是&#xff08; &#xff09;。 A、在服务、接口与协议的区别上不很清楚 B、网络接口层本身并不是实际的一层 C、它不能区分数据链路和物理层 D、传输层对…

超出人类思维的「系统0」:AI正在创造一种新的思维方式吗?

在大众的认知中&#xff0c;人类的思维分为系统 1&#xff08;System 1&#xff0c;直觉的、快速的、无意识的、自动思考&#xff09;和系统 2&#xff08;System 2&#xff0c;有逻辑的、缓慢的、有意识的、计划和推理&#xff09;。 如今&#xff0c;一种不同于 System 1 和…

袋鼠云秋季发布会圆满落幕,AI驱动让生产力数智化

在当今时代&#xff0c;AI 的发展如汹涌浪潮&#xff0c;其速度之快超越了任何历史时期。它以前所未有的迅猛之势&#xff0c;渗入到各个领域的不同场景之中&#xff0c;悄然重塑着商业模式与人们的生活方式。 在 AI 逐渐成为企业基础属性的背景下&#xff0c;袋鼠云举办秋季发…

linux 高级 I/O

高级 I/O 1. 阻塞 I/O 与非阻塞 I/O2. 阻塞 I/O 所带来的困境3. 何为 I/O 多路复用以及原理select()函数介绍poll()函数介绍总结 4. 何为异步 I/O 以及原理5. 存储映射 I/O7. 文件加锁 1. 阻塞 I/O 与非阻塞 I/O 这里举个例子&#xff0c;譬如对于某些文件类型&#xff08;读管…

centos7配置keepalive+lvs

拓扑图 用户访问www.abc.com解析到10.4.7.8&#xff0c;防火墙做DNAT将访问10.4.7.8:80的请求转换到VIP 172.16.10.7:80&#xff0c;负载均衡器再将请求转发到后端web服务器。 实验环境 VIP&#xff1a;负载均衡服务器的虚拟ip地址 LB &#xff1a;负载均衡服务器 realserv…

【亚马逊云科技】Amazon Bedrock搭建AI服务

前言 大模型应用发展迅速&#xff0c;部署一套AI应用的需求也越来越多&#xff0c;从头部署花费时间太长&#xff0c;然而亚马逊科技全托管式生成式 AI 服务 Amazon Bedrock&#xff0c;Amazon Bedrock 简化了从基础模型到生成式AI应用构建的复杂流程&#xff0c;为客户铺设了…

「C/C++」C/C++ 之 判断语句

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

git快速合并代码dev->master

需求&#xff1a; 日常开发都是在dev分支进行开发&#xff0c;但是dev代码开发测试完成后&#xff0c;需要将dev代码合到master主分支上 开始合并代码&#xff1a; 一、场景 一个代码仓库&#xff0c;包含两个分支&#xff0c;一个是master&#xff0c;另一个是dev&#xff1b…

uniapp使用uni-push模拟推送

uniapp使用uni-push模拟推送 第一步先去uniapp开发者中心添加开通uni-push功能 这里的Android 应用签名可以先用测试的官网有,可以先用这个测试 官方测试链接文档地址 在项目中的配置文件勾选 组件中使用 如果要实时可以去做全局ws //消息推送模版uni.createPushMessage(…

前沿技术与未来发展第一节:C++与机器学习

第六章&#xff1a;前沿技术与未来发展 第一节&#xff1a;C与机器学习 1. C在机器学习中的应用场景 C在机器学习中的应用优势主要体现在高效的内存管理、强大的计算能力和接近底层硬件的灵活性等方面。以下是 C 在机器学习领域的几个主要应用场景&#xff1a; 1.1 深度学习…

Java程序设计:spring boot(10)——单元测试

1 pom.xml 测试依赖添加 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId> </dependency> 2 Service业务方法测试 以 UserService 为例&#xff0c;src/test/java ⽬录下添…

解决edge浏览器无法同步问题

有时候电脑没带&#xff0c;但是浏览器没有同步很烦恼。chrome浏览器的同步很及时在多设备之间能很好使用。但是edge浏览器同步没反应。 在这里插入图片描述 解决方法&#xff1a; 一、进入edge浏览器点击图像会显示未同步。点击“管理个人资料”&#xff0c;进入后点击同步&…

21.java异常:关于java异常的学习笔记。 异常的分类,异常体系结构,异常处理机制

关于java异常的学习笔记 什么是异常异常的分类异常体系结构ErrorException异常处理机制IDEA中很重要的快捷键什么是异常 实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格…

记录一次mmpretrain训练数据并转onnx推理

目录 1.前言 2.代码 3.数据形态【分类用】 4.配置文件 5.训练 6.测试-分析-混淆矩阵等等&#xff0c;测试图片效果等 7.导出onnx 8.onnx推理 9.docker环境简单补充 1.前言 好久没有做图像分类了&#xff0c;于是想用商汤的mmclassification快速搞一波&#xff0c;发现已…

gb28181-sip注册流程

gb28181-sip注册流程 当客户端第一次接入时&#xff0c;客户端将持续向Server端发送REGISTER消息&#xff0c;直到Server端回复"200 OK"后结束 它的注册流程如下图&#xff1a; 注册流程&#xff1a; 1 . SIP代理向SIP服务器发送Register请求&#xff1a; 第1行表…