精华内容
下载资源
问答
  • binder原理
    2021-05-14 12:54:17

    Binder 是 Android 中的一种跨进程通信方式。从 Android Framework 角度来说 Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager 等等)和相应 ManagerService 的桥梁;从 Android 应用层来说 Binder 是客服端与服务端进行通信的媒介。

    1、什么是 Binder?

    • 直观来说,Binder 是 Android 中的一个类,它继承了 IBinder 接口
    • 从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在 linux 中没有。
    • 从 Android Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager,etc)和相应 ManagerService 的桥梁。
    • 从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当你 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 的服务。

    2、为什么要使用 Binder?

    1. 性能:移动设备中如果广泛的使用跨进程通信机制肯定会对通信机制提出严格的要求,而 Binder 相比较传统的进程通信方式更加的高效。
    2. 安全:由于传统进程通信方式没有对通信的双方和身方做出严格的验证,只有上层协议才会去架构,如 socket 连接的 IP 地址可以人为的伪造。而 Binder 身份校验也是 android 权限模式的基础。

    3、Binder 的线程管理

    每个 Binder 的 Server 进程会创建很多线程来处理 Binder 请求,可以简单的理解为创建了一个 Binder 的线程池吧(虽然实际上并不完全是这样简单的线程管理方式),而真正管理这些线程并不是由这个 Server 端来管理的,而是由 Binder 驱动进行管理的。

    一个进程的 Binder 线程数默认最大是 16,超过的请求会被阻塞等待空闲的 Binder 线程。理解这一点的话,你做进程间通信时处理并发问题就会有一个底,比如使用 ContentProvider 时(又一个使用 Binder 机制的组件),你就很清楚它的 CRUD(创建、检索、更新和删除)方法只能同时有 16 个线程在跑。

    4、Binder 的工作流程

    1. 客户端首先获取服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用”,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法一样。

    2. 客户端通过调用服务器代理对象的方式向服务器端发送请求。

    3. 代理对象将用户请求通过 Binder 驱动发送到服务器进程。

    4. 服务器进程处理用户请求,并通过 Binder 驱动返回处理结果给客户端的服务器代理对象。

    5. 客户端收到服务端的返回结果。

    5、AIDL 的工作流程

    • 服务端

    服务端首先要创建一个远程 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。

    • 客户端

    首先绑定服务端的 Service,绑定成功后,将服务端返回的 Binder 对象转化成 AIDL 接口所属的类型,接着就可以调用 AIDL 中的方法了。

    6、Binder 有什么优势?

    • 性能方面

      • 共享内存 0 次数据拷贝
      • Binder 1 次数据拷贝
      • Socket/管道/消息队列 2 次数据拷贝
    • 稳定性方面

      • Binder:基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好
      • 共享内存:虽然无需拷贝,但是控制复杂,难以使用
      • 从稳定性的角度讲,Binder 机制是优于内存共享的。
    • 安全性方面

      • 传统的 IPC 没有任何安全措施,安全依赖上层协议来确保。
      • 传统的 IPC 方法无法获得对方可靠的进程用户 ID/进程 UI(UID/PID),从而无法鉴别对方身份。
      • 传统的 IPC 只能由用户在数据包中填入 UID/PID,容易被恶意程序利用。
      • 传统的 IPC 访问接入点是开放的,无法阻止恶意程序通过猜测接收方地址获得连接。
      • Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

    7、Binder 是如何做到一次拷贝的?

    主要是因为 Linux 是使用的虚拟内存寻址方式,它有如下特性:

    • 用户空间的虚拟内存地址是映射到物理内存中的
    • 对虚拟内存的读写实际上是对物理内存的读写,这个过程就是内存映射
    • 这个内存映射过程是通过系统调用 mmap()来实现的

    Binder 借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝

    8、Binder 机制是如何跨进程的?

    1. Binder 驱动

      • 在内核空间创建一块接收缓存区,
      • 实现地址映射:将内核缓存区、接收进程用户空间映射到同一接收缓存区
    2. 发送进程通过系统调用(copy_from_user)将数据发送到内核缓存区。由于内核缓存区和接收进程用户空间存在映射关系,故相当于也发送了接收进程的用户空间,实现了跨进程通信。

    9、Bundle传递数据为什么需要序列化?

    序列化,表示将一个对象转换成可存储或可传输的状态。序列化的原因基本三种情况:

    1. 永久性保存对象,保存对象的字节序列到本地文件中;

    2. 对象在网络中传递;

    3. 对象在IPC间传递。

    10、为什么 Intent 不能传递大数据?

    Intent 携带信息的大小其实是受 Binder 限制。数据以 Parcel 对象的形式存放在 Binder 传递缓存中。如果数据或返回值比传递 buffer 大,则此次传递调用失败并抛出 TransactionTooLargeException 异常。

    Binder 传递缓存有一个限定大小,通常是 1Mb。但同一个进程中所有的传输共享缓存空间。多个地方在进行传输时,即时它们各自传输的数据不超出大小限制,TransactionTooLargeException 异常也可能会被抛出。在使用 Intent 传递数据时,1Mb 并不是安全上限。因为 Binder 中可能正在处理其它的传输工作。不同的机型和系统版本,这个上限值也可能会不同。

    11、Binder IPC 实现原理

    Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

    比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代 I/O 读写,提高文件读取效率。

    而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

    一次完整的 Binder IPC 通信过程通常是这样:

    • 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
    • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
    • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

    12、Binder的内存拷贝过程

    相比其他的IPC通信,比如消息机制、共享内存、管道、信号量等,Binder仅需一次内存拷贝,即可让目标进程读取到更新数据,同共享内存一样相当高效,其他的IPC通信机制大多需要2次内存拷贝。

    Binder内存拷贝的原理为:A为Binder客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder驱动的内核空间,由于进程B在打开Binder设备(/dev/binder)时,已将Binder驱动的内核空间映射(mmap)到自己的进程空间,所以进程B可以直接看到Binder驱动内核空间的内容改动。

    更多相关内容
  • Binder是一种进程间通信机制,基于开源的OpenBinder实现;...5.众多插件化框架的设计原理等等这些问题的背后都与Binder有莫大的关系,要弄懂上面这些问题理解Bidner通信机制是必须的。我们知道Android应用程序是由Ac
  • binder原理

    2012-06-28 19:37:25
    从结构上来说Android Binder系统是一种服务器/客户机模式,包括Binder Server、Binder Client和Android Binder驱动,实际的数据传输就是通过Android Binder驱动来完成的,这里我们就来详细的介绍Android Binder驱动...
  • ❤️Android Binder原理图解❤️

    多人点赞 热门讨论 2021-10-22 12:01:10
    Binder 是 Android 系统中进程间通信机制(IPC)的一种方式,它是这些进程间通讯的桥梁。正如其名"粘合剂"一样,它把系统中各个组件粘合到了一起,是各个组件的桥梁。

    关注:微信公众号「帅次」,获取更多内容。

            之前了解到进程与多进程,涉及多进程不可避免的遇到了进程间通信,说到进程间通信,Binder 成了一道绕不过的坎。接下来咱们逐一了解。

    🔥 什么是进程间通信

            进程间通信(IPC,Inner Process Comunication),就是指不同进程之间的信息传递。

            进程是系统进行资源分配和调度的基本单位,是操作系统的结构的基础;一个应用至少有一个进程,一个进程中有包含了多个线程(线程是CPU调度的最小单位),进程相当于是线程的ViewGroup,线程相当于操作系统分配个进程的View。

    🔥 什么是 Binder

            Binder 是 Android 系统中进程间通信机制(IPC)的一种方式,它是这些进程间通讯的桥梁。正如其名"粘合剂"一样,它把系统中各个组件粘合到了一起,是各个组件的桥梁。

    • 应用层:是一个能发起通信的Java类。
      • Client:是对 Binder 代理对象,是 Binder 实体对象的一个远程代理。

      • Server:是 Server 中的 Binder 实体对象。

    • 机制:是一种进程间通信机制。

    • 驱动:是一个虚拟物理设备驱动;

    如startActivity的简图:

            这里就用到了 Binder 通信,你会发现这里还有 Socker 通信,那我为什么要用 Binder 而不用 Socket。

    🔥 Android 中 IPC 的方式

    名称特点使用场景
    Bundle只能传输实现了序列化或者一些Android支持的特殊对象适合用于四大组件之间的进程交互
    文件不能做到进程间的即时通信,并且不适合用于高并发的场景适合用于SharedPreference以及IO操作
    ContentProvider可以访问较多的数据,支持一对多的高并发访问,因为ContentProvider已经自动做好了关于并发的机制适合用于一对多的数据共享并且需要对数据进行频繁的CRUD操作
    Socket通过网络传输字节流,支持一对多的实时通信,但是实现起来比较复杂适合用于网络数据共享
    Messenger底层原理是AIDL,只是对其做了封装,但是不能很好的处理高并发的场景,并且传输的数据只能支持Bundle类型多进程、单线程且线程安全
    AIDL功能强大,使用Binder机制,支持一对多的高并发实时通信,但是需要处理好线程同步一对多并且有远程进程通信的场景

    🔥 Binder 优势

    出发点Binder共享内存Socket
    性能拷贝一次无需拷贝拷贝两次
    特点基于C/S架构,易用性高控制复杂,易用性差基于C/S架构,通用接口,传输效率低、开销大
    安全每个APP分配UID,同时支持实名和匿名依赖上层协议,访问接入点是开放的不安全依赖上层协议,访问接入点是开放的不安全

    通过以上对比,Android 最终选择了自建一套兼顾好用、高效、安全的 Binder。

    • 好用:基于C/S架构,易用性高

    • 高效:用 mmap() 进行内存映射,只需一次拷贝

    • 安全强:每个 APP 分配UID(进程的身份证号),同时支持实名(系统服务)和匿名(自己创建的服务)

            可以让自己的服务前往 ServiceManager 注册,注册后实名。

    🔥 Linux 传统的 IPC 原理

            了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此,在介绍 Binder 跨进程通信原理之前,我们先聊聊 Linux 系统下传统的进程间通信是如何实现。

    💥 基本概念

    由上图看出:

    • 进程隔离。

    • 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)。

    • 系统调用:用户态/内核态。

    🌀 进程隔离

            操作系统中,进程与进程间内存是不共享的。SCC 进程无法直接访问 Service 进程的数据。SCC 进程和 Service 进程之间要进行数据交互就得采用进程间通信(IPC)。

    🌀 进程空间划分

            现在操作系统都是采用的虚拟存储器。操作系统的核心是内核独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)

    • 内核空间(Kernel Space):是系统内核运行的空间;

    • 用户空间(User Space):是用户程序运行的空间。

    所有内核空间(虚拟地址)都映射在同一块物理内存,这样就实现了内存共享(所有进程可通过IPC访问)。

            为了保证安全性,它们之间是隔离的。即使用户程序蹦了,内核也不受影响。

    🌀 系统调用

            进程内 用户空间 & 内核空间 进行交互 需通过系统调用,主要通过函数:

    • copy_from_user():将用户空间的数据拷贝到内核空间;

    • copy_to_user():将内核空间的数据拷贝到用户空间。

            用户态:当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态);

            内核态:当一个进程执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。

            系统调用是用户空间访问内核空间的唯一方式

    💥 传统 IPC 通信原理

    如图,这就是 Sokcet的拷贝两次

    当然目前 Linux 已经引入 Binder 通信机制。

    🔥 Binder IPC原理

    上面整了一堆 Linux 下的 IPC 相关概念及原理,接下来我们正式介绍下 Binder IPC 的原理。

    💥 Binder 采用分层架构设计

    • 应用层: 对于应用通过调用startActivity()然后调用 AMP.startService , 经过层层调用,最终必然会调用到AMS.startService。

    • Framework: 客户类BinderProxy和服务类Binder(Binder通信是采用C/S架构, Android系统的基础架构便已设计好Binder在Java )。

    • Native层: 对于Native层,可以直接使用BpBinder和BBinder(当然这里还有JavaBBinder)即可, 对于上一层Framework 的通信也是基于这个层面。

    • Kernel: 这里是Binder Driver, 前面3层都跑在用户空间,对于用户空间的内存资源是不共享的,每个Android的进程只能运行在自己进程所拥有的虚拟地址空间, 而内核空间却是可共享的. 真正通信的核心环节还是在Binder Driver。

    💥 Binder 驱动

            在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)

    💥 Binder IPC 内存映射

            Binder IPC 正是基于内存映射(mmap)来实现的,一次完整的 Binder IPC 通信过程通常是这样:

    • 1、Binder 驱动在内核空间创建一个数据接收缓存区

    • 2、在内核空间开辟一块内核缓存区
      • 建立内核缓存区和内核中数据接收缓存区之间的映射关系

      • 内核中数据接收缓存区和接收进程用户空间地址映射关系

    • 3、发送数据完成了一次进程间的通信。
      • 发送方进程通过系统调用 copy_from_user() 将数据 拷贝 到内核中的内核缓存区;

      • 由于内核缓存区和数据接收缓存区存在内存映射,因此也就相当于把数据发送到了数据接收缓存区;

      • 由于数据接收缓存区和进程的用户空间存在内存映射因此也就相当于把数据发送到了接收进程的用户空间。

            内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

    Binder传值限制

    • 原来的 BINDER_VM_SIZE:((1 * 1024 * 1024) - 4096 * 2)

    • 现在的BINDER_VM_SIZE:((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)

    sysconf(_SC_PAGE_SIZE):这个函数用来获取系统执行的配置信息。例如页大小、最大页数、cpu个数、打开句柄的最大个数等等。

            这个值表示你Binder最多传这么多,超出就失败。

    💥 Android Binder 原理图

    🌀 Bind 原理图

            Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager 以及 Binder 驱动,其中 ServiceManager 用于管理系统中的各种服务。

    此处的ServiceManager是指Native层的ServiceManager(C++),并非指framework层的ServiceManager(Java) 原因:

    所以,原理图可表示为以下:

    🌀 Bind 原理图交互

            Client、Server、ServiceManager属于进程空间的用户空间,不可进行进程间交互(下图虚线表示)。

            所以他们都通过与 Binder 驱动 进行交互的,从而实现IPC通信方式。

            所以,原理图可表示为以下:

    🌀 Bind 原理图交互路线

            到这里 Binder 原理算是搞定了。不知道你懂了多少,有疑问可以联系我,我们一起探讨。下一篇咱们一起学习 Binder 在 Android 中的具体实现。

    展开全文
  • 一. Binder 概述 简单介绍下什么是 Binder。...对于 Binder 更全面的定义,等我们介绍完 Binder 通信原理后再做详细说明。 1.1 为什么必须理解 Binder ? 作为 Android 工程师的你,是不是常常会有这样的疑问:..

    一. Binder 概述

    简单介绍下什么是 Binder。Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是粘和不同的进程,使之实现通信。对于 Binder 更全面的定义,等我们介绍完 Binder 通信原理后再做详细说明。

    1.1 为什么必须理解 Binder ?

    作为 Android 工程师的你,是不是常常会有这样的疑问:

    • 为什么 Activity 间传递对象需要序列化?
    • Activity 的启动流程是什么样的?
    • 四大组件底层的通信机制是怎样的?
    • AIDL 内部的实现原理是什么?
    • 插件化编程技术应该从何学起?等等...

    这些问题的背后都与 Binder 有莫大的关系,要弄懂上面这些问题理解 Bidner 通信机制是必须的。

    我们知道 Android 应用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大组件中的一个或者多个组成的。有时这些组件运行在同一进程,有时运行在不同的进程。这些进程间的通信就依赖于 Binder IPC 机制。不仅如此,Android 系统对应用层提供的各种服务如:ActivityManagerService、PackageManagerService 等都是基于 Binder IPC 机制来实现的。Binder 机制在 Android 中的位置非常重要,毫不夸张的说理解 Binder 是迈向 Android 高级工程的第一步。

    1.2 为什么是 Binder ?

    Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢?主要是基于性能稳定性安全性几方面的原因。

    性能

    首先说说性能上的优势。Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。

    注:各种IPC方式数据拷贝次数,此表来源于Android Binder 设计与实现 - 设计篇

    稳定性

    再说说稳定性,Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。共享内存虽然无需拷贝,但是控制负责,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的。

    安全性

    另一方面就是安全性。Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

    基于上述原因,Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求,这就是 Binder。

    最后用一张表格来总结下 Binder 的优势:

    二. Linux 下传统的进程间通信原理

    了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此,在介绍 Binder 跨进程通信原理之前,我们先聊聊 Linux 系统下传统的进程间通信是如何实现。

    2.1 基本概念介绍

    这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍,然后逐步展开,向大家说明传统的进程间通信的原理。

    上图展示了 Liunx 中跨进程通信涉及到的一些基本概念:

    • 进程隔离
    • 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
    • 系统调用:用户态/内核态

    进程隔离

    简单的说就是操作系统中,进程与进程间内存是不共享的。两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。

    进程空间划分:用户空间(User Space)/内核空间(Kernel Space)

    现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。

    简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

    系统调用:用户态与内核态

    虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。

    Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。

    当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

    当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。

    系统调用主要通过如下两个函数来实现:

    copy_from_user() //将数据从用户空间拷贝到内核空间
    copy_to_user() //将数据从内核空间拷贝到用户空间

    2.2 Linux 下的传统 IPC 通信原理

    理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。

    通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copyfromuser() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copytouser() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。如下图:

    这种传统的 IPC 通信方式有两个问题:

    1. 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;
    2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

    三. Binder 跨进程通信原理

    理解了 Linux IPC 相关概念和通信原理,接下来我们正式介绍下 Binder IPC 的原理。

    3.1 动态内核可加载模块 && 内存映射

    正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。

    在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫  Binder 驱动(Binder Dirver)。

    那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。

    这就不得不通道 Linux 下的另一个概念:内存映射

    Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

    内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

    3.2 Binder IPC 实现原理

    Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

    比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

    而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

    一次完整的 Binder IPC 通信过程通常是这样:

    1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
    2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区接收进程用户空间地址的映射关系;
    3. 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

    如下图:

    四. Binder 通信模型

    介绍完 Binder IPC 的底层通信原理,接下来我们看看实现层面是如何设计的。

    一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。

    4.1 Client/Server/ServiceManager/驱动

    前面我们介绍过,Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

    Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。

    通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 http://www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 http://www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 http://www.google.com 对应的服务器。

    Android Binder 设计与实现一文中对 Client、Server、ServiceManager、Binder 驱动有很详细的描述,以下是部分摘录:

    Binder 驱动
    Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

    ServiceManager 与实名 Binder
    ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。

    细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDER SETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体( 这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

    Client 获得实名 Binder 的引用
    Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。

    4.2 Binder 通信过程

    至此,我们大致能总结出 Binder 通信过程:

    1. 首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
    2. Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
    3. Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

    我们看到整个通信过程都需要 Binder 驱动的接入。下图能更加直观的展现整个通信过程(为了进一步抽象通信过程以及呈现上的方便,下图我们忽略了 Binder 实体及其引用的概念):

    4.3 Binder 通信中的代理模式

    我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。

    前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

    当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

    4.4 Binder 的完整定义

    现在我们可以对 Binder 做个更加全面的定义了:

    • 从进程间通信的角度看,Binder 是一种进程间通信的机制;
    • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
    • 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理
    • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。

    五. 手动编码实现跨进程调用

    通常我们在做开发时,实现进程间通信用的最多的就是 AIDL。当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。

    但是无论是从可读性还是可理解性上来看,编译器生成的代码对开发者并不友好。比如一个 BookManager.aidl 文件对应会生成一个 BookManager.java 文件,这个 java 文件包含了一个 BookManager 接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类,Stub 又是 BookManager 的静态内部类,这就造成了可读性和可理解性的问题。

    Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 BookManager、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。

    因此便于大家理解,下面我们来手动编写代码来实现跨进程调用。

    5.1 各 Java 类职责描述

    在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

    • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
    • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
    • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
    • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

    5.2 实现过程讲解

    一次跨进程通信必然会涉及到两个进程,在这个例子中 RemoteService 作为服务端进程,提供服务;ClientActivity 作为客户端进程,使用 RemoteService 提供的服务。如下图:

    那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?还记得我们前面介绍过的 IInterface 吗,它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 BookManager 接口,BookManager 继承自 IIterface,表明服务端具备什么样的能力。

    /**
     * 这个类用来定义服务端 RemoteService 具备什么样的能力
     */
    public interface BookManager extends IInterface {
    
        void addBook(Book book) throws RemoteException;
    }

    只定义服务端具备什么样的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。

    public abstract class Stub extends Binder implements BookManager {
    
        ...
    
        public static BookManager asInterface(IBinder binder) {
            if (binder == null)
                return null;
            IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
            if (iin != null && iin instanceof BookManager)
                return (BookManager) iin;
            return new Proxy(binder);
        }
    
        ...
    
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
    
                case INTERFACE_TRANSACTION:
                    reply.writeString(DESCRIPTOR);
                    return true;
    
                case TRANSAVTION_addBook:
                    data.enforceInterface(DESCRIPTOR);
                    Book arg0 = null;
                    if (data.readInt() != 0) {
                        arg0 = Book.CREATOR.createFromParcel(data);
                    }
                    this.addBook(arg0);
                    reply.writeNoException();
                    return true;
    
            }
            return super.onTransact(code, data, reply, flags);
        }
    
        ...
    }

    Stub 类中我们重点介绍下 asInterface 和 onTransact

    先说说 asInterface,当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。

    接下来我们就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 BookManager 接口。

    public class Proxy implements BookManager {
    
        ...
    
        public Proxy(IBinder remote) {
            this.remote = remote;
        }
    
        @Override
        public void addBook(Book book) throws RemoteException {
    
            Parcel data = Parcel.obtain();
            Parcel replay = Parcel.obtain();
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                if (book != null) {
                    data.writeInt(1);
                    book.writeToParcel(data, 0);
                } else {
                    data.writeInt(0);
                }
                remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
                replay.readException();
            } finally {
                replay.recycle();
                data.recycle();
            }
        }
    
        ...
    }

    我们看看 addBook() 的实现;在 Stub 类中,addBook(Book book) 是一个抽象方法,Server 端需要去实现它。

    • 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
    • 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

    在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 addBook() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

    这样一次跨进程调用就完成了。

    最后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信,加深对 Binder 通信过程的理解。

    受个人能力水平限制,文章中难免会有错误。如果大家发现文章不足之处,欢迎与我沟通交流。

    本文在写作过程中参考了很多文章、书籍和源码,其中有很多描述和图片都借鉴了下面的文章,在这里感谢大佬们的无私分享!

    参考资料如下:

    展开全文
  • Binder原理

    2022-07-23 23:43:03
    Binder原理

    面试

    • 面试官

    谈一谈Binder的原理和实现一次拷贝的流程

    • 心理分析

    能问出该问题,面试官对binder的理解是非常深入的。想问求职者对Android底层有没有深入理解

    • 求职者

    应该从linux进程通信原理的两次拷贝说起,然后引申为什么binder却只有一次拷贝 ,最后阐述内核空间 与用户空间的定义

    1、 Linux 下传统的进程间通信原理

    了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此,在介绍 Binder 跨进程通信原理之前,我们先聊聊 Linux 系统下传统的进程间通信是如何实现。

    1.1 基本概念介绍

    这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍,然后逐步展开,向大家说明传统的进程间通信的原理。

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    上图展示了 Liunx 中跨进程通信涉及到的一些基本概念:

    • 进程隔离
    • 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
    • 系统调用:用户态/内核态

    1.1.1 进程隔离

    简单的说就是操作系统中,进程与进程间内存是不共享的。两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)

    1.1.2 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)

    现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。

    简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    1.1.3 系统调用:用户态与内核态

    虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。

    Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。

    • 内核态
      当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

    • 用户态
      当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。

    系统调用主要通过如下两个函数来实现:

    copy_from_user() //将数据从用户空间拷贝到内核空间
    copy_to_user() //将数据从内核空间拷贝到用户空间
    
      
    • 1
    • 2

    1.2 Linux 下的传统 IPC 通信原理

    理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。

    通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中

    同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。如下图:

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    这种传统的 IPC 通信方式有两个问题:

    1. 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;
    2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

    2. Binder 跨进程通信原理

    理解了 Linux IPC 相关概念和通信原理,接下来我们正式介绍下 Binder IPC 的原理。

    2.1 动态内核可加载模块 && 内存映射

    正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。

    在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。

    那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。

    这就不得不通道 Linux 下的另一个概念:内存映射

    Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。

    内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

    内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

    具体可以参考链接了解mmap,腾讯的mmkv框架也用了mmap技术。

    2.2 Binder IPC 实现原理

    Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

    比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);

    通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

    而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

    一次完整的 Binder IPC 通信过程通常是这样:

    1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区
    2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系
    3. 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

    如下图:

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    3. Binder 通信模型

    介绍完 Binder IPC 的底层通信原理,接下来我们看看实现层面是如何设计的。

    一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。

    3.1 Client/Server/ServiceManager/驱动

    前面我们介绍过,Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动

    其中

    • Client、Server、Service Manager 运行在用户空间,
    • Binder 驱动运行在内核空间**

    其中

    • Service Manager 和 Binder 驱动由系统提供,
    • 而 Client、Server 由应用程序来实现。

    Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。

    通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 http://www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 http://www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 http://www.google.com 对应的服务器。

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    Android Binder 设计与实现一文中对 Client、Server、ServiceManager、Binder 驱动有很详细的描述,以下是部分摘录:

    • Binder 驱动
      Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

    • ServiceManager
      ServiceManager 与实名 Binder ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。
       
      注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。
       
      Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。
       
      驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。
       
      细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!
       
      Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。
      ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。
      当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。
      其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。
      也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。
       
      类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

    • Client
      Client 获得实名 Binder 的引用 Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。
      Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。
      ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。
       
      从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。

    3.2 Binder 通信过程

    至此,我们大致能总结出 Binder 通信过程:

    1. 首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
    2. Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
    3. Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

    我们看到整个通信过程都需要 Binder 驱动的接入。下图能更加直观的展现整个通信过程(为了进一步抽象通信过程以及呈现上的方便,下图我们忽略了 Binder 实体及其引用的概念):

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    4 Binder 通信中的代理模式

    我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。

    前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。
     
    当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

    当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    5 Binder 的完整定义

    现在我们可以对 Binder 做个更加全面的定义了:

    • 从进程间通信的角度看,Binder 是一种进程间通信的机制;
    • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
    • 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理
    • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。

    6. 手动编码实现跨进程调用

    通常我们在做开发时,实现进程间通信用的最多的就是 AIDL
     
    当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。

    但是无论是从可读性还是可理解性上来看,编译器生成的代码对开发者并不友好。比如一个 BookManager.aidl 文件对应会生成一个 BookManager.java 文件,这个 java 文件包含了一个 BookManager 接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类,Stub 又是 BookManager 的静态内部类,这就造成了可读性和可理解性的问题。

    Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 BookManager、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。

    因此便于大家理解,下面我们来手动编写代码来实现跨进程调用。

    6.1 各 Java 类职责描述

    在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

    • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
    • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
    • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
    • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

    6.2 实现过程讲解

    一次跨进程通信必然会涉及到两个进程,在这个例子中 RemoteService 作为服务端进程,提供服务;ClientActivity 作为客户端进程,使用 RemoteService 提供的服务。如下图:

    腾讯面试题——谈一谈Binder的原理和实现一次拷贝的流程

    那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?还记得我们前面介绍过的 IInterface 吗,它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 BookManager 接口,BookManager 继承自 IIterface,表明服务端具备什么样的能力。

    /**
     * 这个类用来定义服务端 RemoteService 具备什么样的能力
     */
    public interface BookManager extends IInterface {
     void addBook(Book book) throws RemoteException;
    }
    
      

      只定义服务端具备什么样的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。

      public abstract class Stub extends Binder implements BookManager {
       ...
       public static BookManager asInterface(IBinder binder) {
       if (binder == null)
       return null;
       IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
       if (iin != null && iin instanceof BookManager)
       return (BookManager) iin;
       return new Proxy(binder);
       }
       ...
       @Override
       protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
       switch (code) {
       case INTERFACE_TRANSACTION:
       reply.writeString(DESCRIPTOR);
       return true;
       case TRANSAVTION_addBook:
       data.enforceInterface(DESCRIPTOR);
       Book arg0 = null;
       if (data.readInt() != 0) {
       arg0 = Book.CREATOR.createFromParcel(data);
       }
       this.addBook(arg0);
       reply.writeNoException();
       return true;
       }
       return super.onTransact(code, data, reply, flags);
       }
       ...
      }
      

        Stub 类中我们重点介绍下 asInterface 和 onTransact。

        先说说 asInterface,当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。

        接下来我们就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 BookManager 接口。

        public class Proxy implements BookManager {
         ...
         public Proxy(IBinder remote) {
         this.remote = remote;
         }
         @Override
         public void addBook(Book book) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel replay = Parcel.obtain();
         try {
         data.writeInterfaceToken(DESCRIPTOR);
         if (book != null) {
         data.writeInt(1);
         book.writeToParcel(data, 0);
         } else {
         data.writeInt(0);
         }
         remote.transact(Stub.TRANSAVTION\_addBook, data, replay, 0);
         replay.readException();
         } finally {
         replay.recycle();
         data.recycle();
         }
         }
         ...
        }
        
         

          我们看看 addBook() 的实现;在 Stub 类中,addBook(Book book) 是一个抽象方法,Server 端需要去实现它。

          • 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
          • 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

          在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。

          正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。

          最终通过一系列的函数调用,

          • Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;
          • 驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。
          • 最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 addBook() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

          这样一次跨进程调用就完成了。

          参考链接

          展开全文
        • Android系统中,每个应用程序是由Android的Activity,Service,Broadcast,ContentProvider这四剑客的中一个或多个组合而成,这四剑客所涉及的多进程间的通信底层都是依赖于Binder IPC机制。例如当进程A中的Activity...
        • Binder原理剖析.pptx

          2019-11-13 09:43:45
          介绍Binder原理,PPT中含有备注,这些备注有利于理清讲解思路。希望通过这个PPT能让大家对Binder的原理有一个宏观的认识。
        • 1. Binder概述 从IPC角度来说:Binder是Android中的一种跨进程通信方式,该通信方式在linux中没有,是Android独有; 从Android Driver层:Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder; 从...
        • 一. 前言 这篇文章我酝酿了很久,参考了很多资料,读了很多源码,却...Binder 之复杂远远不是一篇文章就能说清楚的,本文想站在一个更高的维度来俯瞰 Binder 的设计,最终帮助大家形成一个完整的概念。对于应用层开...
        • Android Binder 原理解析

          千次阅读 2019-01-01 22:27:59
          目录 一. 前言 二. Binder 概述 三. Linux 下传统的进程间通信原理 ...3.1 基本概念介绍 ...3.2 Linux 下的传统 IPC 通信原理 ...4.2 Binder IPC 实现原理 五. Binder 通信模型 5.1 Client / Server / Service...
        • Binder原理系列

          2019-11-12 10:24:38
          下面推荐几篇皇叔的Binder原理系列文章: 刘望舒Binder系列 下面从概述->Java层Binder->Native层Binder->Binder驱动大致流程进行分析Binder原理: 1.Linux的IPC通信原理 用户空间需要访问内核空间,就...
        • 1. Linux 已经拥有管道消息队列 / 共享内存 / 信号量socket 等 IPC 手段为什么还要开发 Binder 这种进程间通讯方式 Binder 设计的需求是什么 使用 Client-Server 的通信方式有利于 Android 应用开发 基于 Client-...
        • 原文地址:写给 Android 应用工程师的 Binder 原理剖析 文章目录前言一. Binder 概述1.1 为什么必须理解 Binder ?1.2 为什么是 Binder ?性能稳定性安全性二. Linux 下传统的进程间通信原理2.1 基本概念介绍进程隔离...
        • 原理:通信双方利用内存的共享文件来传递信息; 信号(sinal) 异步通信方式,软件层对中断机制的一种模拟,例如内核通知用户空间进程发生了哪些系统事件 不适用于信息交换,适用于进程中断控制; 信号量(semophore
        • Binder 原理

          2022-02-11 11:48:46
          收到Binder驱动通知后,Server 进程通过回调Binder对象onTransact()进行数据解包 & 调用目标方法 public class Stub extends Binder { // 复写onTransact() @Override boolean onTransact(int code, Parcel ...
        • 众所周知binder是Android极具特色的IPC方式,也可以说是Android系统中最重要的部分。Binder系统的基石则是Binder驱动。接下来就看下Binder驱动是怎么在底层提供支持的。binder驱动的代码并不在aosp的源码中而是在...
        • Android Binder原理

          2018-12-12 10:43:07
          管理Binder模型的线程是采用Binder驱动的线程池,并由Binder驱动自身进行管理(而不是由Service线程来管理) 一个进程的Binder线程默认最大是16,超过的请求会被阻塞等待空间的Binder线程 所以在进程通信时处理...
        • Android Binder原理初探

          2020-01-18 17:42:23
          1、Binder的通信原理 动态内核可加载模块&&内存映射 在Android Binder开卷中所说的IPC通信模型中所描述的,跨进程通信需要内核空间做支持。传统的IPC机制如管道,Socket都是内核的一部分,因此通过内核...
        • Binder原理是掌握系统底层原理的基石,也是进阶高级工程师的必备知识点,这篇文章不会过多介绍Binder原理,而是讲解学习Binder前需要的掌握的知识点。 我认为学好Binder原理的秘诀主要有两点: 了解Binder原理涉及...
        • Binder原理是掌握系统底层原理的基石,也是进阶高级工程师的必备知识点,这篇文章不会过多介绍Binder原理,而是讲解学习Binder前需要的掌握的知识点。 1.Linux和Android的IPC机制种类 IPC全名为inter-Process ...
        • Binder 概述 Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是粘和不同的进程,使之...
        • Android Binder原理解析

          2020-11-28 14:57:26
          Binder是一个很深入的话题,本篇文章不打算深入探讨Binder的底层细节,重点介绍Binder的使用以及上层原理Binder 是Android中的一个类,他实现了IBinder接口。Binder是Android中的一种跨进程通信方式,Binder还...
        • Binder 之复杂远远不是一篇文章就能说清楚的,本文想站在一个更高的维度来俯瞰 Binder 的设计,最终帮助大家形成一个完整的概念。对于应用层开发的同学来说,理解到本文这个程度也就差不多了。希望更加深入理解 ...
        • 什么是 Java Binder: Java Binder 可以粗略的理解为 Java 层的 Binder 通信。与 Native Binder 就像是两个世界对同一事物的不同映射,但其实还是有层级关系的。Java Binder 在 Native Binder 之上,通过 JNI 注册...

        空空如也

        空空如也

        1 2 3 4 5 ... 20
        收藏数 31,409
        精华内容 12,563
        关键字:

        binder原理