Android: Binder: 彻底顿悟Android Binder

news2024/9/25 17:11:25

Binder机制可谓是Android 知识体系的重中之中,作为偏底层的基础组件,平时我们很少关注它,但是它却无处不在,这也是android面试考察点之一,本篇将从流程上将Binder通信过一遍。

 

文章目录

1:Binder作用

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

3:ServiceManager进程的作用

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

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

6:Binder服务端数据接收

7:Binder通信全流程图

一:Binder作用 

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

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

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

基于这种方式衍生了几种 IPC(进程间通信);

1:管道,消息队列,socket等。

而Android采用了新的机制:Binder ,这种方式只需要一次数据拷贝,并且Binder更加安全。

二:进程与 Binder驱动如何通信 

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

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

那么常见的系统调用函数接口如:

open/ read/ wirte/ioctl/ close/ mmap/ fork 等。 

用户进程与BInder通信步骤

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

2:  通过ioctl 与Binder驱动进行数据通信: ioctl(mDriverFD, BINDER_WRITE_READ,&bwr);

其中参数 bwr是读写数据结构。

三:ServiceManager进程的作用 。

先看下:Binder Client、Binder Server、ServiceManager之间关系

为了方便:ServiceManager简称为 SM。

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

我们知道,C和S是不同的进程,那么C如何拿到S的Binder引用了?

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

这样似乎可以讲通,但是SM也是一个单独的进程,那么S,C如何与SM通信了?这个实际上就陷入了 先有鸡还是先有蛋的问题。

其实面对这样的问题,一般就是系统给初始化。什么意思?

系统先将 SM作为特殊的 Binder(handle = 0)提前放入Binder驱动,当C,S想要获取SM的Binder引用,只需要获取 handle = 0的Binder即可。

3.1 SeerviceManager注册进 Binder驱动 

 SM注册进Binder驱动后就会 等待Binder驱动的消息,这里列举两个常见的处理消息的场景

1:其他进程添加服务到 SM这里。

2:其他进程向SM插叙服务。

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

 它主要记录 name和 handle字段。

1:当SM收到添加服务的指令后,从Binder驱动里取出 handle和name ,并构造结构体插入到 链表。

2:当SM收到查询服务的指令后,从 Binder驱动里取出 name, 并找到链表里对应name的 handle, 然后写入 Binder驱动,Binder驱动最后转发给 查询进程。

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

4.1:其它进程找到 SM

我们以振动服务作为切入点来分析

tranceBeginAndSlog("StartVibratorService")
vibrator = new VibratoeService(context);
ServiceManager.addService("vibrator", vibrator);
tranceEnd();

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

system_server /  addService

 源码可以看到,主要分为两步:

1:先找到 ServiceManager

2:  往ServiceManager里添加服务 

getIserviceManager()

 

 

其中 BInderINternal.getContextObject()是 native方法。我们重点看下 Native层。

serviceManager对应的 BpBinder对象,没有找到的话,创建新的并存取缓存里,方便下次直接获取。

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

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

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

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

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

4.2:  添加服务扫ServiceManager 

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

#ServiceManagerNative.ServiceManagerProxy
public void addService(string name,  IBinder service,boolean allowIsolated,int dumpPriority)
{

    // 构造Parcel
    Parcel data = Parcle.obtain();
    Parcel reply = Parcle.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驱动上。

此时Binder驱动收到的消息包括但不限于:

1:服务的名字

2:ServiceManager的handle

3:  BBinder对象指针

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

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

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

 BBinder作用

java层传递的是Binder对象,那么如何与 Native的 BBinder关联起来了?

重点就在:下面这行代码。

Parcel.writeStrongBinder(Binder)

 

 也就是说,Server端的 java Binder对象在 Native层的代表是BBinder。

Binder驱动记录了BBinder的地址,当有消息过来时通过找到BBinder对象进而找到java层的 Binder对象,最终调用 Binder.onTransact()。

5: 进程从ServiceManager获取服务流程 

5.1:其他进程找到SM

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

我们看看,应该如何获取振动服务。

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

与添加服务类似,我们首先找到 SM,找到SM过程 和4.1是一样的,这里不必赘述。

5.2 从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驱动。

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

1:服务的名字

2: ServiceManager的 handle 

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

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

5.3:handle转换成Binder对象

问: 我们知道SM维护的链表中 handle是整形的,但是调用者接收的是Binder对象,这两个是怎么结合起来的了?

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

(SM将查询到结果后将 handle写入驱动,然后从驱动将结果存入 reply字段,最后通过 reply拿到 Binder引用,重点方法是: reply.readStrongBinder())。

 

 

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

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

6:Binder服务端数据接收 

在微信进程拿到振动服务(在system_server进程里)的Binder(BinderProxy)后,就可以调用振动方法了,然后指令发送给驱动,驱动通过振动服务的handle找到对应的服务 BBinder指针,从而调用服务的接收方法。

下面来看下system_server进程是如何接收并处理指令的。

system_server进程启动的时候,就会开启Binder线程池,并等待驱动数据到来。

当sytem_server进程添加振动服务到SM时,会将 java层的 Binder转为 Native层的BBinder,并将 BBinder对象指针写入Binder驱动。

当微信进程调用 system_server接口时:

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

2: Binder驱动根据handle找到 system_server进程

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

如此一来:微信程序成功调用了振动服务,也就是所一次 Client和 Service端的通信就完成了。

7:Binder通信全流程图 

 整个设计中,核心是 handle,现在我们简单的总结一下

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

2:通过handle驱动找到server端进程,进而调用BBinder(Native层),与此对应的是java层的Binder。

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

以上:就是整个Binder机制的梳理过程,此间省略了Binder驱动里的映射逻辑,可以将BInder驱动当做一个黑盒,更重要的是  Binder客户端和服务端是如何进行映射的。

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

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

相关文章

STM32F7-Discovery使用ITM作为调试工具

关于代码的调试手段,我在自己的一篇文章(http://bbs.ickey.cn/index.php?appgroup&actopic&id54944链接中的《STM32F030 Nucleo-开发调试的经验USART的重要性.pdf》)中已经详细谈到,为什么在调试中我们通常使用J-Link或ULINK或ST-Link(ST)或Ope…

机器学习——细节补充

1.matplotlib与seaborn的区别 来源:https://geek-docs.com/matplotlib/matplotlib-ask-answer/difference-between-matplotlib-and-seaborn.html 2.%matplotlib inline使图片嵌入notebook,而不需要使用show()方法 3.IPython与python:IPyth…

中小企业如何选择进销存软件?

企业信息化转型趋势的推动,让很多中小企业也开启了转型的探索。对于企业,一款合适的进销存管理软件,绝对是转型之路上的必备工具,可以帮助企业对经营中的采购、库存、销售等环节进行有效管理监督。 目前,市面上的各种…

three.js 的渲染结构

理解three.js 的渲染结构 1 three.js 的渲染 Three.js 封装了场景、灯光、阴影、材质、纹理和三维算法,让你不必再直接用WebGL 开发项目。three.js 在渲染三维场景时,需要创建很多对象,并将它们关联在一起。 下图便是一个基本的three.js 渲…

Python通知Epic白嫖游戏信息

每周都有免费游戏 - Epic Games 近期看到Epic在送游戏,目前每周都会有活动白嫖。 身为白嫖党,肯定要操作一下。 游戏列表:Epic Games Store 每周免费游戏(331) | indienova GameDB 游戏库 大致思路: 1、…

把teamtalk中的网络库(netlib)拆出来单独测试实现双工通信效果

这篇文章的基础是上一篇对于将teamtalk中的线程池,连接池单独拆出来的讲解 不是说这个网络库会依赖线程池,连接池,而是上一篇文章中讲了一些base目录中的文件,并且这个网络库会依赖一些base目录里的文件, 文末会将所有…

基于fpga的自动售货机(三段式状态机)

目录 1、VL38 自动贩售机1 题目介绍 思路分析 代码实现 仿真文件 2、VL39 自动贩售机2 题目介绍: 题目分析 代码实现 仿真文件 3、状态机基本知识 1、VL38 自动贩售机1 题目介绍 设计一个自动贩售机,输入货币有三种,为0.5/1/2元&…

JS概览 (JS基础 DOM BOM)

目录 JavaScript JS基础 JS数据类型 函数 变量的作用域 作用域链 预解析 DOM DOM树 获取元素的方法 事件高级 注册和解绑事件 DOM事件流 BOM 和DOM的区别 window 对象的常见事件 window.onload JS执行机制 具体的执行流程 例子 JavaScript JS基础 JS数据类…

鉴源论坛 · 观模丨基于AUTOSAR的TTCAN通信协议的形式化建模与分析

作者 | 郭建 上海控安可信软件创新研究院特聘专家 版块 | 鉴源论坛 观模 汽车工业发展至今,硬件方面如车身材料、发动机等已无太大升值空间,而汽车电子则有着广阔的前景。为此各大汽车厂商对汽车电子的研究都投入了大量的人力财力。2003 年&#xff0c…

链式二叉树的代码总结

今天我带来链式二叉树的代码总结。 目录前言链式二叉树代码实现的五个文档二叉树的例子前序遍历中序遍历后序遍历层序遍历求结点个数的函数求叶子的个数的函数求k层结点个数的函数查找某一个值的函数求二叉树高度的函数判断二叉树是否是完全二叉树的函数开辟二叉树结点的函数销…

【设计模式】工厂方法模式

简单工厂模式的弊端 在简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类。简单工厂模式最大的缺点是当有新产品要加入到系统中时,必…

实现isReactive和isReadonly

08_实现isReactive和isReadonly 一、实现isReactive isReactive: 检查一个对象是否是由 reactive 创建的响应式代理。 1. 单元测试 // src/reactivity/tests/reactive.spec.tsimport { reactive, isReactive } from ../reactive;describe(reactive, function () {it(happy pa…

Callable接口

前言 获取多线程的方法,我们都知道有三种,还有一种是实现Callable接口 实现Runnable接口实现Callable接口实例化Thread类使用线程池获取Callable接口 Callable接口,是一种让线程执行完成后,能够返回结果的 在说到Callable接口…

【Unity天空盒】卡通渲染中如何实现云的消散效果

写在前面 完成大气渲染之后,接下来就是考虑云渲染了。因为我想做的天空盒本身是想跟着这位大佬Unity 卡通渲染 程序化天空盒 - 知乎里叙述的进程来的,里面云实现的是原神里的云,原神又是在崩3的基础上加上了消散效果。但现在能找到的一些教程…

线程中的sleep, yield, join

1. 前言 今天以具体实例的方法来详细记录下实战中的sleep, yield, join。 到底是什么意思,应该怎么用呢??? 2. 适合人群 对该类方法的概念比较模糊的人 3. 开始 3.1 sleep 此方法是一个静态方法,可以通过类名直接调…

【MyBatis】安装 + 框架搭建 + 使用 + 优化(全程一条龙服务讲解~)

目录 前言 一、准备工作 1.1、下载MyBatis 1.2、数据库设计 二、搭建框架 2.1、创建Maven项目 2.2、jar包、引入依赖 2.3、创建MyBatis核心配置文件 2.4、映射文件 2.5、通过junit测试功能 2.6、框架优化 三、小结——注意事项 前言 本篇全程从0到1搭建MyBatis框架…

Python编程 简单春节倒计时教程(附源代码)

作者简介:一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.应用的技术 1.Tkinter 2.PHotoimage 函数 3.label组件 二.效果图 三…

pytorch 咖啡豆识别

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍦 参考文章地址: 365天深度学习训练营-第P6周:好莱坞明星识别🍖 作者:K同学啊一、前期准备 1.设置GPU import torch from torch import nn …

2022 年 pnpm 为什么这么火?

pnpm是 Node.js 的替代包管理器。它是 npm 的直接替代品,但速度更快、效率更高。 为什么更有效率?当你安装一个包时,我们将它保存在你电脑上的全局存储中,然后我们从它创建一个硬链接而不是复制。也就是说:对于模块的…

8.移动端学习-rem适配方案

1.适配方案 1、当设备尺寸发生变化时,页面宽高等比例变化 2、使用媒体查询根据不同设备按比例设置html字体大小,页面元素使用rem做单位,当html字体大小变化,元素尺寸也会发生变化,从而达到等比缩放的适配 2.rem实际开…