为什么Zygote通信使用socket而不是binder?

2,535 阅读6分钟

Hi 大家好,我是 DHL,大厂程序员,公众号:ByteCode ,在美团、快手、小米工作过。搞过逆向,做过性能优化,研究过系统,擅长鸿蒙、Android、Kotlin、性能优化、职场分享。

本篇文章以面试为主,进阶部分有源码分析,大家可以选择性的看。

之前写了一篇文章分析了 为什么 Android 要采用 Binder 作为 IPC 机制? 评论区有个问题问的非常好,这个问题也是面试中频率非常高的问题。

Q:为什么 Zygote 通信使用 socket 而不是 binder?

在 Android 系统中,zygote 是一个特殊的守护进程,它的主要作用是用来启动新的应用进程。

zygote 进程在系统启动时被创建,之后所有的应用进程都是通过 fork 它来创建的。这种方式可以提高进程启动速度,因为新的进程所需要资源已经在 zygote 进程中加载和初始化一次,进程被创建时会复制 zygote 的共享的资源,避免重复初始化。

至于为什么 zygote 进程使用 socket 通信而不是 Binder 机制,有以下几个原因:

  • 历史原因zygote 的设计和实现早于 Binder 机制的广泛应用。在 Android 的早期版本中,zygote 就已经使用 socket 来监听来自 system_server 的请求,用于创建新的应用进程。

  • 简单性socket 通信是一种比较简单和通用的进程间通信(IPC)机制,它不依赖于 Android 特有的 Binder 机制,也不需要复杂的接口定义。zygote 进程只需要处理相对简单的命令,如启动新的应用进程,因此 socket 通信足以满足需求。

  • 兼容性socket 是一种跨平台的 IPC 机制,可以在不同的操作系统和环境中使用。这意味着 zygote 的实现可以更容易地在不同的 Android 版本或者修改版 Android 系统中保持一致和兼容。

  • 性能:虽然 Binder 机制在 Android 系统中提供了高效的 IPC 通信,但是 zygote 进程的通信需求并不高频,通常只在应用程序启动时使用。因此,使用 socket 通信不会对系统性能造成显著影响。

  • 安全性zygote 进程作为一个系统级别的服务,它的安全性非常重要。使用 UNIX domain socket 可以确保只有系统中的特定服务(如 system_server)才能和 zygote 通信,从而提供了一定程度的安全性。

zygote 使用 socket 通信是出于初始化时机、历史、简单性、兼容性、性能和安全性的考虑。虽然 Binder 提供了更为复杂和强大的 IPC 机制,但对于 zygote 来说,socket 是一个更合适的选择。

进阶结:合源码分析一下 zygote 是如何使用 socket

在 Android 系统中,zygote 进程是负责启动新的应用进程的守护进程。Binder 是 Android 系统特有的一种高效的 IPC 机制,而 zygote 的设计是为了在系统启动时预加载和初始化一系列共享资源,以便快速创建新的应用进程。

zygote 使用的是 UNIX 域套接字(UNIX domain socket),这是一种在相同操作系统内部的进程间通信机制。UNIX 域套接字在性能上相对高效,并且在 Android 系统早期版本中已经存在,因此 zygote 从一开始就使用了它。

在 Android 系统源码中,zygote 进程的启动和监听逻辑通常在 ZygoteInit.java 文件中实现。我们来看一下 zygote 是如何创建和使用 socket,以下是简化后的源码。

public class ZygoteInit {
    // UNIX域套接字名称
    private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";

    public static void main(String argv[]) {
        // ...省略其他初始化代码...

        // 获取zygote套接字的文件描述符
        int fileDescriptor = Integer.parseInt(System.getenv(ANDROID_SOCKET_ENV));
        FileDescriptor fd = new FileDescriptor();
        fd.setInt$(fileDescriptor);

        // 创建ServerSocket用于监听来自system_server的连接请求
        ServerSocket zygoteSocket = new ServerSocket();
        try {
            FileDescriptor fd = createFileDescriptor(socketFD);
            zygoteSocket = new ZygoteServer();
            zygoteSocket.registerServerSocketFromEnv(ANDROID_SOCKET_ENV);
            
            // 开始监听请求
            zygoteSocket.runSelectLoop(abiList);
        } catch (IOException ex) {
            throw new RuntimeException("Error binding to local socket '" + fd + "'", ex);
        }
    }
}

在这段代码中,zygote 进程通过环境变量 ANDROID_SOCKET_zygote 获取了一个 UNIX 域套接字的文件描述符,然后使用这个文件描述符创建了一个 ServerSocket

zygote 进程随后在这个 ServerSocket 上监听来自 system_server 的连接请求。

system_server 或其他系统服务需要创建新的应用进程时,它们通过这个套接字与 zygote 进程通信,发送创建进程的命令。

zygote 进程接收到命令后,会 fork 自身来创建一个新的应用进程。

使用 UNIX 域套接字而不是 Binder 的一个原因是,Binder 通信需要在 Android 运行时(ART)和 Binder 驱动已经初始化之后才能使用,而 zygote 进程在这些组件之前就已经启动。

因此,使用 UNIX 域套接字可以在更早的启动阶段建立通信,而无需依赖于 Binder 驱动的初始化。此外,UNIX 域套接字在 Linux 系统中是一种标准的通信机制,其实现简单且稳定,适用于 zygote 这样的基础系统服务。

全文到这里就结束了,感谢你的阅读,如果文章对你有帮助,欢迎在看、点赞、分享给身边的小伙伴,你的点赞是我持续更新的动力。

更多大厂面试题,欢迎前往微信搜索小程序「猿面试」查看。微信小程序(猿面试)包含了 Java、Android、鸿蒙和ArkTS设计模式算法和数据结构 相关内容,


推荐阅读