Binder解析精炼

Binder原理解析精炼

1 注册服务

  1. Server进程向binder驱动向Binder驱动发起服务注册请求

    向Binder驱动申请创建一个XXXService的Binder的实体,Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用

  2. Binder驱动将注册请求转发给ServiceManager进程

    Binder驱动将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXX的Service

  3. ServiceManager进程添加该server进程:从Binder驱动里取出handle和name,并构造结构体插入到链表。

此时,servicemanager进程拥有了Server进程的信息

在这里插入图片描述

2 获取服务

  1. client向Binder驱动发起获取服务的请求,传递要获取服务的名称

  2. Binder驱动将该请求转发给ServiceManager进程

  3. ServiceManager查找到client需要的server对应的服务信息:从Binder驱动里取出name,并找到链表里相同的name,找到后取出handle,最后写入到Binder驱动

  4. 通过Binder驱动将上述服务信息返回给client进程

    client进程与server进程已经建立联系

    此时,如果有Client向SM发送申请服务XXXService的请求,那么SM就可以在查找表中找到该Service的Binder引用,并把Binder引用(XXXBpBinder)返回给Client。

3 使用服务

  1. client端调用getService通过handler构造client端的BpBinder(native层),与此对应的是Java层的BinderProxy;

  2. 将ServiceManager的handler与请求服务名写入binder驱动,binder驱动通过SM拿到对应server的handler,从而找到Server进程,进而调用BBinder(Native层),与此对应的是Java层的Binder;

    对应server的handler就是该server的Binder引用

  3. 将拿到的server的handler转换为BpBinder,然后转为java层的BinderProxy

  4. client接口调用:调用BinderProxy.transact(),转为native层的BpBinder.transact(),然后转为handler,将数据写入binder驱动

  5. Server端读取Binder驱动数据,找到BBinder对象,然后转为Binder对象,调用Binder.onTransact()完成对应接口调用,并通过Binder驱动返回处理结果给客户端的服务器代理对象

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

4.完整流程

在这里插入图片描述
在这里插入图片描述

5 常见问题

1 Binder的数据结构

Binder在传输数据中的表述:flat_binder_object
Binder对象类型
Binder实体在驱动中的表述:binder_node
Binder引用在驱动中的表述:binder_ref
Binder 进程、线程结构:binder_proc和binder_thread
Binder收发数据包结构:binder_transaction_data
Binder写操作命令字: BC_XXX
Binder读操作命令字: BR_XXX

2 一次拷贝

将内核缓存区和接收进程用户空间地址映射到一个接收缓存区

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

  • 在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系

  • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便实现了一次拷贝。

在这里插入图片描述

3 既然使用内存映射技术可以减少数据拷贝次数,为什么发送方进程不做内存映射?

发送方进程也做内存映射确实可以实现数据 0 拷贝传输,这就属于是以共享内存的方式进行 IPC 了。但是从以下两个角度来看,还是使用 Binder 的方式比较适合 Android

  1. 从性能角度来看: Binder 的数据只要拷贝一次,性能仅次于共享内存,且 RPC 调用的数据通常较小,因此拷贝操作对性能的影响并不大。
  2. 从稳定性和复杂性角度来看:Binder是基于C/S架构的,简单解释下C/S架构,是指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰明朗,Server端与Client端相对独立,稳定性较好;而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题

4 oneway 机制

  • 非 oneway 的 aidl 接口,调用方线程会被阻塞,直到被调用方进程返回为止
  • oneway 的 aidl 接口,不会阻塞调用方线程,而是立即返回。在Android Framework 中 AMS 启动 Activity 时就是使用的 oneway 的方式,不会因为应用进程而阻塞了AMS进程的线程
  • oneway 的还有一个特点,它是串行化的,binder 驱动内部有一个队列,会将它一个一个发送给接收进程

那么问题来了,oneway 的接口立即返回,怎么拿到被调用方进程的处理结果呢?

首先,如果我们不需要关注返回结果的,建议使用 oneway,如果需要返回结果,也可以用 oneway,有两种方式:

  1. 注册监听,将 callback 也定义成 aidl 接口,然后通过注册方式向接收方进程传递,接收方进程拿到 callback,通过 binder 机制可以向发送方回调数据
  2. 直接在定义方法的时候,传入 aidl 定义的 callback 接口,同上机制

但是要注意,通过单独注册监听的方式,如果需要反注册的话,接受方进程维护的监听列表,要使用 RemoteCallbackList 来存储。因为发送方的 callback 和接收方拿到的 callback 不是同一个对象,而 RemoteCallbackList 内部通过以 callback 对应的 binder 作为 key (虽然不是同一个对象,但是 binder 是同一个),所以可以保证注册和反注册正常,同时 RemoteCallbackList 内部做了线程安全保障(注册和反注册都是synchronized 方法),不需要自己额外处理多线程问题

5 Binder 通信的数据大小限制

在进程创建的时候会为 Binder 创建一个1M -8k的缓冲区用于跨进程通信时的数据传输

Binder 调用中同步调用优先级大于 oneway(异步)的调用

为了充分满足同步调用的内存需要,所以将 oneway 调用的内存限制到申请内存上限的一半

如果超过1M-8k这个上限就就会抛出这个异常,而且这个缓存区时当前进程内的所有线程共享的,线程最大数量为16个,如同时间内总传输大小超过了1M,也会抛异常。

另外在Activity启动的场景比较特殊,因为Binder 的通信方式为两种,一种是异步通信,一种是同步通信,异步通信时数据缓冲区大小被设置为了原本的1半

6 为什么要用Binder

简单的回答:性能:相比传统的Socket更高效;安全:安全性高,支持通信双方进行身份验证

7 Binder为啥用Parcelable做序列化

Serializable使用简单,但是开销很大(大量I/O操作),Parcelable是Android中的序列化方式,使用起来麻烦,但是效率很高,是Android推荐的方式。Parcelable主要用在内存序列化上,如果要将对象序列化到存储设备或者通过网络传输也是可以的,但是会比较复杂,这两种情况建议使用Serializable。

序列化

序列化是指将一个对象转化为二进制或者是某种格式的字节流,将其转换为易于保存或网络传输的格式的过程,反序列化则是将这些字节重建为一个对象的过程。Serializable和Parcelable接口可以完成对象的序列化。

1)Serializable

Serializable是Java提供的序列化接口,使用时只需要实现Serializable接口并声明一个serialVersionUID(用于反序列化)

2)Parcelable

A. writeToParcel:将对象序列化为一个Parcel对象,将类的数据写入外部提供的Parcel中
B. describeContents:内容接口描述,默认返回0
C. 实例化静态内部对象CREATOR实现接口Parcelable.Creator,需创建一个数组(newArray(int size)) 供外部类反序列化本类数组使用;createFromParcel创建对象
ribeContents:内容接口描述,默认返回0
C. 实例化静态内部对象CREATOR实现接口Parcelable.Creator,需创建一个数组(newArray(int size)) 供外部类反序列化本类数组使用;createFromParcel创建对象
D. readFromParcel:从流里读取对象,写的顺序和读的顺序必须一致。

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值