我的编程空间,编程开发者的网络收藏夹
学习永远不晚

AndroidBinder通信原理图文详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

AndroidBinder通信原理图文详解

前言

Binder机制可谓是Android 知识体系里的重中之重,作为偏底层的基础组件,平时我们很少关注它,而它却是无处不在,也是Android 面试易考察的点之一。网上很多文章,要么知识点比较陈旧,要么源码贴一堆,要么没有成体系地分析,导致读者一知半解,似是而非。
本篇将从流程上将Binder通信过一遍,尽量多用图展示。
通过本篇文章,你将了解到:

Binder的作用

进程与Binder驱动如何通信

ServiceManager进程的作用

进程添加服务到ServiceManager的流程

进程从ServiceManager获取服务的流程

Binder服务端数据接收

Binder 通信全流程图

1. Binder的作用

先看Linux下进程地址映射关系:

我们知道,对象调用本身就是地址空间的访问。

如上,进程之间各自访问各自的内存地址,它们之间无法直接访问对方的地址,也就是说微信不能直接调用支付宝提供的接口。而内核具有访问其它进程地址空间的权限,因此微信可以将消息发送给内核,让内核帮忙转发给支付宝,这种方式叫做:存储/转发方式。

由此衍生的几种IPC(进程间通信)如:管道、消息队列、socket等,而Android 上采用了新的机制:

Binder,相比传统的方式,Binder只需要一次数据拷贝,并且Binder更安全。

Binder机制是Android 里用来做IPC的主要方式。

2. 进程与Binder驱动如何通信

既然得要内核进行消息中转,那么Binder驱动得运行在内核空间,而事实上也确实如此,Binder驱动加载后在内核空间运行,进程只需要和Binder驱动取得联系,通过Binder驱动联系另一个进程,那么一次消息的传送过程就可以实现了。

内核提供提供一系列的系统调用接口给用户进程使用,当用户进程想要访问内核时,只需要调用对应的接口,此时代码就会从用户空间切换到内核空间执行。

常见的系统调用函数如:open/read/write/ioctl/close/mmap/fork 等。

与Binder驱动通信分两步:

打开Binder驱动:open("/dev/binder", O_RDWR | O_CLOEXEC)

通过ioctl 与Binder驱动进行数据通信:ioctl(mDriverFD, BINDER_WRITE_READ, &bwr)
bwr 为读写数据结构

3. ServiceManager进程的作用

Binder Client、Binder Server、ServiceManager关系

为方便起见,ServiceManager简称SM。

Binder 设计为C/S架构,C为Client(客户端),S为Server(服务端),Server端提供接口(服务)给Client端使用,而这个服务是以Binder引用的形式提供的。

由之前的知识可知,C和S是不同的进程,那么C如何拿到S的Binder引用呢?

你可能会说,当然是SM了,S先将Binder引用存放在SM里,当C需要的时候向SM查询即可。

这么看似乎讲得通了,那问题又来了,SM也是一个单独的进程,那S、C如何与SM进行通信呢?这就陷入了先有鸡还是先有蛋的死循环了。

实际上C、S、SM之间都是依靠Binder通信,只是SM作为特殊的Binder(handle=0)提前放入了Binder驱动里,当C、S想要获取SM的Binder引用,只需要获取handle=0的Binder即可。

这么说没有太直观的印象,我们一步步剖析。

ServiceManager注册进Binder

SM 注册进Binder驱动后就会等待来自Binder驱动的消息,这里列出了两个最常见的处理消息的Case:

其它进程添加服务到SM里

其它进程向SM查询服务

SM里维护着一个链表,链表的元素是结构体:

主要记录的是name和handle字段。
当SM收到添加服务的指令后,从Binder驱动里取出handle和name,并构造结构体插入到链表。
当SM收到查询服务的指令后,从Binder驱动里取出name,并找到链表里相同的name,找到后取出handle,最后写入到Binder驱动。

4. 进程添加服务到ServiceManager的流程

其它进程找到SM

现在SM已经翘首以盼其它进程的请求了,接着来看看如何添加一个服务到SM里。
以Java层添加服务为例,我们选择振动服务作为切入点分析。

在system_server 进程里构造振动服务(VibratorService继承自Binder),并添加到SM里。

可以看出,分两步:

先找到ServiceManager

往ServiceManager里添加服务

getIServiceManager()继续往下:

BinderInternal.getContextObject() 是native方法,后续流程较多,我们用图表示。

寻找ServiceManager的过程涉及到Java层和Native层,主要的重点在Native层查找 ServiceManager对应的BpBinder对象,没有找到的话则创建新的并存入缓存里以备下次直接获取。

ProcessState里维护了一个单例,每个进程只有一个ProcessState对象,创建ProcessState时候就会去打开Binder驱动,同时会设置Binder线程池里线程个数等其它参数

Native层构造BpBinder(handle=0表示该BpBinder是ServiceManager在客户端的引用),再构造BinderProxyNativeData持有BpBinder。

构造BinderProxy对象并持有BinderProxyNativeData,也就是间接持有BpBinder

最后构造了ServiceManagerProxy对象,它实现了IServiceManager接口,它的成员变量mRemote指向了BinderProxy

可以看出,获取ServiceManager的过程并不是真正去获取ServiceManager的Binder对象,而是获取它在当前进程的代理:BpBinder

添加服务到ServiceManager

既然找到了SM的Binder代理,接下来看看如何使用它给SM添加服务。

    #ServiceManagerNative.ServiceManagerProxy
    public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
            throws RemoteException {
        //构造Parcel
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        //写入Binder
        data.writeStrongBinder(service);
        data.writeInt(allowIsolated ? 1 : 0);
        data.writeInt(dumpPriority);
        //通过BinderProxy发送
        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
        reply.recycle();
        data.recycle();
    }

其中IPCThreadState与线程相关,不同的线程会维护一个单例。

由此可见,最终还是通过BpBinder发送消息,进而发送到Binder驱动。

此时驱动收到的信息包括不限于:

服务的名字

ServiceManager的handle

BBinder对象指针

驱动建立服务handle和BBinder对象指针的映射关系,并将服务的名字和服务的handle传递给ServiceManager(通过ServiceManager handle查找)。

ServiceManager拿到消息后建立映射关系,等待其它进程的请求。

至此,进程添加服务到ServiceManager过程已经分析完毕,用图表示如下:

BBinder作用

Java层传递的是Binder对象,如何与Native的BBinder关联起来呢?
重点在:

Parcel.writeStrongBinder(Binder)

也即是说Server端的Java Binder对象在Native层的代表是BBinder。
Binder驱动记录了BBinder的地址,当有消息过来时通过找到BBinder对象进而找到Java层的Binder对象,最终调用Binder.onTransact()。

5. 进程从ServiceManager获取服务的流程

其它进程找到SM

振动服务添加完成后,某些进程想要获取振动服务进行振动,比如微信收到消息后需要振动用以提示用户。
接着来看看如何获取振动服务。

    private void vibrate() {
        //获取振动服务
        Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
        //开始振动
        vibrator.vibrate(1000);
    }

与添加服务类似,想要获取服务先要找到SM,找SM的过程上边分析过了,此处不再细说。

从ServiceManager获取服务

    #ServiceManagerNative.ServiceManagerProxy
    public IBinder getService(String name) throws RemoteException {
        //构造Parcel
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        //写入名字
        data.writeString(name);
        //通过BinderProxy发送
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

由此可见,最终还是通过BpBinder发送消息,进而发送到Binder驱动。
此时驱动收到的信息包括不限于:

服务的名字

ServiceManager的handle

Binder驱动收到消息后,找到SM,并将服务的名字传给SM,SM从自己维护的链表里找到服务名相同的节点,最终取出该服务的handle,发送给Binder驱动。

用图表示如下:

对比添加服务流程和获取服务流程,两者前半部分都很相似,都是先拿到SM的BpBinder引用,然后写入驱动,最后由SM进程处理。只是对于获取服务流程来说,还需要将查询的结果(handle)写入驱动返回给调用方(对应图上红色部分)。

到这,大家可能会有疑惑了:"handle是整形值,而微信获取的振动服务是一个Binder对象,这两者是怎么结合起来的呢?"

handle转换为Binder对象

handle表示的即是Binder服务端在客户端的索引句柄,只要客户端拿到了handle,它就能通过Binder驱动调用到服务端。

        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();

再回过头看看获取服务的代码,当微信进程将查询命令发给Binder驱动后就等待驱动回复的结果,SM查询到结果后将handle写入驱动,而后微信进程从驱动将结果读出并将结果存入reply字段。
最后通过reply拿到Binder引用,也就是说重点在reply.readStrongBinder()方法。
直接看图:

如上,通过驱动返回的handle构造BpBinder,最终封装为Java层的BinderProxy。

至此,获取服务流程就结束了,用图展示简化的流程

6. Binder服务端数据接收

微信进程拿到振动服务(在system_server进程里)的Binder(BinderProxy)后,就可以调用振动方法了,而后指令发送给驱动,驱动通过振动服务的handle找到对应的服务BBinder指针,从而调用服务的接收方法。
微信进程发送指令给Binder驱动前面已经分析过,重点来看看system_server进程是如何接收并处理指令的。

system_server进程启动的时候就会开启Binder线程池,并等待驱动数据到来。
当system_server进程添加振动服务到SM时,会将Java层的Binder转为Native层的BBinder,并将BBinder对象指针写入Binder驱动。
当微信进程调用system_server接口时:

微信进程调用BpBinder.transact()将handle和数据写入Binder驱动

Binder驱动根据handle找到system_server进程

system_server进程从驱动拿到数据,并取出BBinder指针,最终调用到system_server进程Java层的Binder.onTransact()

如此一来,微信成功调用了振动服务,也就是说一次Client到Server端的通信就完成了。

7. Binder 通信全流程图

纵观Binder机制设计,最核心的点是handle。

通过handle构造Client端的BpBinder(Native层),与此对应的是Java层的BinderProxy

通过handle,驱动找到Server端进程,进而调用BBinder(Native层),与此对应的是Java层的Binder

通过handle的一系列中转,Client.transact()成功调用了Server.onTransact(),一次Binder通信就过程就完成了

最后,用一张图总结Binder机制的全过程:

以上就是整个Binder机制的梳理过程,此间省略了Binder驱动里的映射逻辑,可以将Binder驱动当做一个黑盒,而更重要的是Binder客户端和服务端是如何进行映射的。
Binder流程比较绕,尤其是IPCThreadStsate作为客户端的发送和服务端的数据接收的实体,需要区分不同的场景。
当然,jni基础知识必不可少。

本文基于Android 10

以上就是Android Binder 通信原理图文详解的详细内容,更多关于Android Binder 通信原理的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

AndroidBinder通信原理图文详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

AndroidBinder通信原理图文详解

这篇文章主要为大家介绍了AndroidBinder通信原理图文详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

python网络通信图文详解

目录1. 常用的网络协议2. 数据传输过程3. 两台电脑通信4. IP地址网络号与主机号的确定规则 5. 最开始集线器解决多台电脑通信 6. 使用交换机进行网络通信7. 使用udp协议实现内网瘫痪8. 使用路由器连接多个网络9. 浏览器
2022-06-02

Iframe跨窗口通信原理详解

这篇文章主要为大家介绍了Iframe跨窗口通信原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

(手写)PCA原理及其Python实现图文详解

目录1、背景2、样本均值和样本方差矩阵3、PCA3.1 最大投影方差3.2 最小重构距离4、Python实现总结1、背景 为什么需要降维呢?因为数据个数 N 和每个数据的维度 p 不满足 N >> p,造成了模型结果的“过拟合”。有两种方法
2022-06-02

DorisJoin优化原理文档详解

这篇文章主要为大家介绍了DorisJoin优化原理文档详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

通过HashMap原理详解entrySet中的疑问

这篇文章主要为大家介绍了通过HashMap原理详解entrySet中的疑问,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

一文详解MySQL Join使用原理

JOIN是一种非常常见的操作,用于将两个或多个表中的数据合并到一个结果集中。MySQL支持多种JOIN类型,本文通过代码示例详细介绍了Join的使用优化,有需要的小伙伴可以参考阅读
2023-05-17

一文详解MySQL Join使用原理

目录Join的类型Joinphp原理Simpe Nested-Loop JoinIndex Nested-Loop Joinblock Nested-Loop JoinJoin优化Join的类型left join,以左表为驱动表,以左表作
2023-04-25

通过手写instanceof理解原型链示例详解

这篇文章主要为大家介绍了通过手写instanceof理解原型链示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-19

SQL通用语法以及分类图文详解

目录mysql的启动和连接&数据模型SQL通用语法及分类Data Definition LanguageDDL - 数据库操作DDL - 表操作表操作 - 数据类型表操作-修改&&删除DDL总结Data Manipulation Langu
2022-12-30

Python图像处理之边缘检测原理详解

边缘检测是图像处理和计算机视觉当中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。本文将通过示例和大家介绍一下边缘检测的原理,希望对大家有所帮助
2022-12-08

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录