Android Framework分析Zygote进程的启动过程

news2024/10/5 21:02:56

在这里插入图片描述Zygote进程是Android系统中的一个重要进程,其主要作用是预热Java虚拟机和启动应用进程。本文将着重分析Zygote进程的启动过程,结合代码注释和示例代码,让读者更好地理解Zygote的内部工作原理。

Zygote进程的启动过程

Zygote进程的启动过程包含几个关键步骤,主要涉及Binder通信、双亲委派模型、系统属性加载和App启动等。下面我们将逐一进行讲解。

主入口函数

Zygote进程的主函数是ZygoteInit.main(),它的主要职责是启动Zygote进程并为其提供额外的参数。该函数主要由以下两部分组成:

public static void main(String[] args) {
    ...
    // 设置JAVA_OPTS环境变量,指定堆大小等Java虚拟机参数
    Os.setenv("JAVA_OPTS", "-Dfile.encoding=UTF-8 -Xmx64m", true);
    ...
    // 创建ZygoteInit对象,并调用其registerZygoteSocket()方法
    // 该方法会创建一个ServerSocket对象,用于监听应用进程的连接请求
    ZygoteInit zygoteInit = new ZygoteInit();
    zygoteInit.registerZygoteSocketIfNeeded(envVars);
    ...
    // 开始监听应用进程的连接请求
    zygoteInit.runSelectLoop(TextUtils.join(",", abis));
}

如上所示,ZygoteInit.main()中主要进行了环境变量、ZygoteInit对象创建以及注入参数等操作,其中registerZygoteSocketIfNeeded()是ZygoteInit对象中最关键的方法之一。

注册Zygote Socket

ZygoteInit.registerZygoteSocketIfNeeded()是ZygoteInit启动过程中最重要的方法之一,它主要用于创建一个ServerSocket对象并将其绑定到一个本地Unix域socket上,这样Zygote进程就可以监听来自应用进程的连接请求了。

@SuppressWarnings("UnusedReturnValue")
private boolean registerZygoteSocketIfNeeded(@NonNull String[] envVars)
        throws IllegalStateException, IOException {
    // 获取Zygote Socket的文件路径
    mZygoteSocketName = Zygote.getConfigurationProperty("ro.zygote", null);
    if (mZygoteSocketName == null) {
        Log.e(TAG, "Error starting Zygote: required property ro.zygote not set");
        throw new RuntimeException("Error starting Zygote: required property ro.zygote not set");
    }

    // 如果文件已存在,则删除相关的unix域socket
    File socketFile = new File(mZygoteSocketName);
    if (socketFile.exists()) {
        socketFile.delete();
    }

    // 创建ServerSocket对象,绑定到本地unix域socket上
    try {
        mZygoteServerSocket = new LocalServerSocket(mZygoteSocketName);
    } catch (IOException ex) {
        Log.e(TAG, "Error binding to local socket '" + mZygoteSocketName + "'", ex);
        throw ex;
    }

    // 获取androidboot.classpath和androidboot.libs属性的值
    String classPath = Zygote.getConfigurationProperty("androidboot.classpath", "");
    String libraryPath = Zygote.getConfigurationProperty("androidboot.libs", "");

    // 设置环境变量BOOTCLASSPATH和ANDROID_ROOT
    final String bootClassPath = !classPath.isEmpty() ? "-Xbootclasspath:" + classPath.replace(':', ',') : "";
    envVars = new String[] {
            computeZygoteHeapSize(),
            "BOOTCLASSPATH=" + bootClassPath,
            "ANDROID_ROOT=" + "/system",
            "ANDROID_DATA=" + "/data",
            "ANDROID_PROPERTY_WORKSPACE=" + "/data/local/tmp",
            "LD_LIBRARY_PATH=" + libraryPath};

    // 设置好环境变量之后关闭Socket,等待应用进程的连接
    mZygoteServerSocket.close();
    return true;
}

如上所示,该方法主要实现了创建ServerSocket对象并接收应用进程的连接请求。在函数内部,最核心的部分是LocalServerSocket类的使用,它是一个继承自ServerSocket的本地socket类,因此它不需要使用网络中断和解封装技术,能够更快地处理传输数据。

接下来,该方法会获取androidboot.classpath和androidboot.libs属性的值,这些属性值是在启动Zygote进程时通过init.rc脚本获取的。

然后,该方法会设置BOOTCLASSPATH和ANDROID_ROOT等环境变量,并调用ZygoteInit中的computeZygoteHeapSize()方法计算出Zygote进程的堆大小。

最后,该方法会关闭ServerSocket对象并等待应用进程的连接请求。

应用进程的连接请求

应用进程的连接请求主要是通过ZygoteInit.runSelectLoop()方法实现的。在该方法中,Zygote进程会不断地监听ServerSocket对象是否有新的socket连接请求,并在接收到新的socket连接请求时,调用ZygoteInit.forkAndSpecialize()方法来创建新的应用进程,并通过socket文件描述符的方式将新进程启动起来。

void runSelectLoop(String abiList) {
    while (true) {
        ...
        // 接收新的连接请求
        ZygoteConnection newPeer = acceptCommandPeer(mZygoteServerSocket);

        // 创建新的进程
        ZygoteProcess zygoteProcess = ZygoteProcess.getZygoteProcess();

        // 通过Socket文件描述符的方式启动新进程
        socketDescriptor = newPeer.getFileDescriptor();
        zygoteProcess.start(socketDescriptor, newArgs, runtimeFlags, null,
                nativeDebuggable, newStdin, newStdout, newStderr, null,
                instructionSet, cdnEnabled, capabilities, permittedCapabilities, effectiveCapabilities);
    }
}

如上所示,ZygoteInit.runSelectLoop()方法主要是循环监听ServerSocket对象,并在接收到新的连接请求时,通过ZygoteProcess.start()方法启动新的App进程。

ZygoteProcess.start()方法是一个比较庞大的方法,其主要流程如下:

public int start(int socketFd, String[] argv, int runtimeFlags,
        String seInfo, boolean debuggerEnabled, FileDescriptor[] descriptors,
        String instructionSet, String invokeWith, long[] fdsToClose, int[] fdsToIgnore,
        boolean startChildZygote, String[] acceptedAbis) {
    ...
    // 创建App进程
    for (String abi : abis) {
        ...
        // 创建新进程,并创建对应的应用类加载器
        try {
            return doPreForkInit(newZygote, socketFd, contentType, argc,
                    argv, instructionSet, invokeWith, acceptedAbis, runtimeFlags, seInfo,
                    stdio_fds, fdsToClose, fdsToIgnore, isTopZygote);
        } catch (IOException e) {
            if (e.getMessage().contains("Too many open files")) {
                // If the kernel runs out of file descriptors, it may start throwing EIO on socket
                // accept(). Since we're likely to recover from an EIO, log it only at a
                // VERBOSE level.
                Log.v(TAG, "IOException on accept: ", e);
            } else {
                Log.e(TAG, "Exception creating new zygote: ", e);
            }
        }
    }
    ...
}

如上所示,doPreForkInit()方法是创建新进程的重要方法之一,它主要作用是创建应用进程的类加载器和执行系统属性文件。

系统属性文件的加载

在Zygote进程启动过程中,加载系统属性文件是一个非常重要的过程。系统属性文件中包含了许多Android系统运行所需的关键信息,例如堆大小、系统密度、语言环境等。

ZygoteInit.doPreload()方法是加载系统属性文件的核心方法。在该方法中,系统会首先扫描/system/build.prop文件和/prop目录中的所有*.prop文件,并将它们的属性信息读取到Properties对象中。然后,针对某些特殊的系统属性,比如ro.build.fingerprint,系统会调用SystemProperties.get()方法获取对应的属性值。

private static void doPreload() {
    // Get a set of default properties from /system/build.prop and /prop files.
    Properties preloadedProps = new Properties();
    preloadedProps.load(new FileInputStream(new File("/system/build.prop")));

    File[] propFiles = new File("/").listFiles(new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return name.endsWith(".prop");
        }
    });

    if (propFiles != null) {
        for (File file : propFiles) {
            try {
                FileInputStream stream = new FileInputStream(file);
                preloadedProps.load(stream);
                stream.close();
            } catch (IOException e) {
                Log.w(TAG, "Unable to read " + file.getName() + ".prop: " + e.getMessage());
            }
        }
    }

    // Look for non-native debuggability override only if we haven't already gotten the value
    // through a native property.
    String nonDebuggable =
            SystemProperties.get(PROP_NONDEBUGGABLE_APPS);
    if (nonDebuggable == null) {
        nonDebuggable = preloadedProps.getProperty(PROP_NONDEBUGGABLE_APPS);
    }

    // Look for a non-sdk graylist override only if we haven't already gotten the value
    // through a native property.
    String nonSdkGreylist =
            SystemProperties.get(PROP_NON_SDK_GREYLIST_APPS);
    if (nonSdkGreylist == null) {
        nonSdkGreylist = preloadedProps.getProperty(PROP_NON_SDK_GREYLIST_APPS);
    }

    ...
}

如上所示,doPreload()方法是ZygoteInit中一个比较重要的方法,它主要是将/system/build.prop和/prop/*.prop中的属性读取到preloadedProps这个Properties对象中。同时,该方法也会调用SystemProperties.get()方法获取ro.build.fingerprint等特殊属性的值。

接下来,将介绍应用进程类加载器的创建过程。

应用进程类加载器的创建

ZygoteProcess.getZygoteProcess().start()方法的主要作用是创建新的应用进程和相应的应用类加载器。ZygoteProcess通过调用NativeStart.main()的方式来启动新进程,并将当前ClassLoader传递给新进程。新进程中调用的 ClassLoader.getSystemClassLoader() 方法就会返回当前的ClassLoader,也就是Zygote进程的应用进程类加载器。

public static int loadZygoteChild(@NonNull ZygoteArguments parsedArgs,
        @Nullable FileDescriptor[] descriptors, ZygoteServer zygoteServer) {
    ...
    // 创建新的进程,调用的是Object.clone()方法
    Process child = Zygote.fork();

    if (child == null) {
        return 1;
    } else if (child == 0) {
        // This is the child process.
        setArgv0(parsedArgs.processName);
        Os.setpgid(0, 0);
        try {
            // 通过类加载器和服务器端Socket描述符的方式启动新应用进程。
            Runnable r = forkAndSpecialize(parsedArgs, Zygote.getForkServerSocket(),
                    Zygote.getUsapPoolSocket(), descriptors, runtimeFlags, gidList);
            r.run();
        } finally {
            Zygote.nativeCloseServerSocket();
            Zygote.nativeCloseUsapPoolSocket();
        }
    }
    return handleChildProc(child, parsedArgs, zygoteServer);
}

如上所示,新应用进程中会通过forkAndSpecialize()方法创建一个应用类加载器,在运行时期,Zygote进程将通过Socket描述符的方式将应用进程类加载器传递给应用进程,并启动应用进程。

应用进程中通过ActivityThread类的main()方法来启动应用,它会在应用进程中启动ActivityThread主线程。

总结

本文介绍了Zygote进程的启动过程,包括Binder通信、双亲委派模型、系统属性加载和App启动等多个方面,通过代码和示例的方式,详细地讲解了Zygote进程启动的内部原理。在Zygote进程启动过程中,系统会加载系统属性文件,创建应用进程类加载器,并传递给应用进程。通过深入研究Zygote进程的启动过程,可以更好地理解Android系统的内部工作原理,并对Android应用的开发、优化和调试等方面有所帮助。

参考资料:

  1. Android Framework源码:https://cs.android.com/android/platform/superproject/

  2. Android Developers官方文档:https://developer.android.com/docs

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

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

相关文章

Python3 里面的四舍五入

目录 1.一般的四舍五入 : 使用内置的round函数 1.1官方文档: 1.2 举例说明: 2.python3里的格式化输出 format 2.1 记忆法则 :填齐宽 逗精类 2.2 format实质就是通过设置精度间接使用了等效round函数,但是不要把格式化输出和四…

chatgpt赋能python:Python是如何帮助确定location的?

Python是如何帮助确定location的? 什么是location? 在SEO中,location指的是特定页面、文章或者商铺在搜索结果中的排名位置。通常来说,更高的location意味着更多的点击率和流量,因此在SEO中,确定location…

独立站思考:Facebook选品测品

导语:对于独立站而言,获取稳定的流量是至关重要的。本文将探讨如何利用Facebook的选品测品功能,精准找到用户并提高点击率,以及如何通过数据分析优化,提高转化率并快速产生订单。 第一部分:精准定位用户的方…

Nodejs五、Express

零、文章目录 Nodejs五、Express 1、初识 Express (1)Express 简介 Express是什么 官方给出的概念:Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。 通俗的理解:Express 的作用和 Node.js 内置的 …

Swift 5.9 有哪些新特性(一)

文章目录 前言if 和 switch 表达式Value 和 Type 参数包 前言 虽然 Swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏、非可复制类型、自定义 actor 执行器等等都将在 Swift 5.9 中推出,再次带来了一个巨大的更…

矩阵补全文献汇总

[1] Nguyen L T , Kim J , Shim B .Low-Rank Matrix Completion: A Contemporary Survey[J].IEEE Access, 2019, PP(99):1-1.DOI:10.1109/ACCESS.2019.2928130. 几根棒子的一篇工作。基本结构可以借鉴。 适用于秩未知的矩阵补全文献汇总 [1] Fornasier M , Rauhut H , Ward…

报表岗位如何快速升职加薪?卷的心态要放平,工具要选对!

最近下班时一直看到做报表的部门每个人埋头苦干,不用说,这是又在忙半年度报告了。 现在,报表内卷现象十分严重,大家可能用的是一样的数据集,虽说每个人输出的报告可能结果差异不大,但懂得怎么利用工具&…

在HR眼里,IE证书早就不值钱了

大家好,我是老杨。 最近项目实在是忙,内容都写的少了一些,真的是有点力不从心的意思,人年纪大了,比不起当初年轻的自己了 和同事领导在一块儿的时间越多,就免不了聊到今年的就业环境。 我不提&#xff0…

Vue中如何进行代码编辑器与实时预览?

Vue中如何进行代码编辑器与实时预览? 在现代Web应用程序中,代码编辑器和实时预览已经成为了必不可少的一部分。Vue作为一款流行的JavaScript框架,也提供了一些工具和库,方便开发者在Vue中集成代码编辑器和实时预览功能。本文将介…

基于Eclipse+Java+Swing+Mysql实现超市销存管理系统

基于EclipseJavaSwingMysql实现超市销存管理系统 一、系统介绍二、功能展示1.登陆2.整体页面3.进货4.售货5.查询6、退出系统 三、数据库四、其它1.其他系统实现五.获取源码 一、系统介绍 系统实现了:商品进货、商品销售、库存查询 、进货查询、 售货查询、退出系统…

Date类(Java)

文章目录 1. 介绍2. 分析3. 方法3.1 Constructor()3.2 getTime()3.3 compareTo()3.4 equals() 1. 介绍 A. 类介绍:类Data表示特定的瞬间,可以精确到毫秒  Date类 有两个其他的函数。它允许把日期解释为年、月、日、小时、分钟和秒值  从 JDK 1.1 开始&…

Python自动化测试的配置层实现方式对标与落地 | 京东云技术团队

Python中什么是配置文件,配置文件如何使用,有哪些支持的配置文件等内容,话不多说,让我们一起看看吧~ 1 什么是配置文件? 配置文件是用于配置计算机程序的参数和初始化设置的文件,如果没有这些配置程序可能…

Vue中如何进行数据可视化关系图展示(如关系图谱)

Vue中如何进行数据可视化关系图展示(如关系图谱) 随着数据分析和可视化技术的发展,越来越多的应用开始使用关系图谱来展示数据之间的关系。在Vue中,我们可以使用第三方库Vis.js来实现关系图谱的展示,并通过Vue组件来进…

Java网络开发(Tomcat同步数据分页)—— 用Jsp语法 到 实现数据的分页展示 到 只看自己的数据 + 模糊查询 迭代升级

目录 引出0.jsp的使用和语法 & 报错和解决(1)后端共享,前端获取 ${pageInfo}(2)如果想获取pageInfo这个对象的某个属性值,用 点 属性 ${pageInfo.pages}(3)如果想回传&#xff…

java 学生信息管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 学生信息管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0…

IDEA执行Maven命令

在工作区的最右侧,IntelliJ IDEA 为我们提供了一个十分实用的窗口:Maven 工具窗口,通过它我们几乎可以完成所有与 Maven 相关的操作。 在 Maven 工具窗口中,我们可以通过以下 3 种方式中执行 Maven 命令: 使用 Run An…

RFID技术在工业自动化和生产流程优化方面的成功应用

您是否好奇于如何在工业场景中利用RFID技术实现更高效的操作和生产流程优化?ANDEAWELL作为国内知名的RFID工业识别设备供应商, 企业国产化替代的首选品牌,将带您深入探索RFID技术在工业领域的应用,揭示其优势和挑战,并…

召回率的概念

召回率 就是查出来的正确的数量除以所有正确的数量;准确率是用你查出来的正确的数量除以所有的数量(包含正确和不正确的数量)。 附上某位大佬的图:

激光盐密灰密测试仪

一、产品特点 KDYM-302L 激光盐密灰密测试仪采用检测技术将灰密测试与盐密测试合二为一,可同时检测出被测绝缘子的灰密度和盐密度,简化了绝缘子污秽检测的流程,非常适合在巡检现场和实验室使用。 二、主要特点 内置测试专用测试软件&#x…

WebP 转换工具

webp WebPcwebp 编码(转换成 WebP)dwebp 解码Android Studio 编码 WebP1、Convert to WebP...2、选项3、压缩4、直接运行即可 WebP Github:https://github.com/webmproject/WebPShop 中文教程:https://developers.google.com/sp…