Binder上层原理浅析
[TOC]
1. IBinder简介
Binder实现IBinder接口,IBinder是一个接口,代表一种跨进程传输的能力,实现此接口,就可以将此对象进行跨进程传递。
根据IBinder的源码介绍及分析,可以了解很多信息(译自IBinder的源码介绍并总结):
1. IBinder是高性能、轻量级进程间通信的基本接口,该接口定义了与远程对象通信的协议。不能直接实现此接口,而要继承自它的实现类Binder
。
2. IBinder主要API是transact()和Binder的onTransact(), 这两个方法进行发送和接收Binder对象,调用trasact的返回数据会在onTransact回调后返回。
场景:调用IBinder的transact()
给IBinder对象发送请求,然后经过Binder的Binder.onTransact()
得到调用,远程操作的目标得到回调。
IBinder的transact方法:
1 | /** |
与他对应的Binder的onTransact方法:
1 | /** |
两个方法的参数一致,参数具体意义是什么?
- code:要执行的动作,类似于Handler的what,自定义Code值需要在
IBinder.FIRST_CALL_TRANSACTION
(0x00000001)和IBinder.LAST_CALL_TRANSACTION
(0x00ffffff)之间。其中IBinder内部定义了部分code:- PING_TRANSACTION:表示要调用
pingBinder()
- DUMP_TRANSACTION:获取Binder内部状态
- SHELL_COMMAND_TRANSACTION:执行一个Shell命令
- INTERFACE_TRANSACTION:询问接收方的接口描述符号
- TWEET_TRANSACTION:发送推文给目标对象
- LIKE_TRANSACTION
- PING_TRANSACTION:表示要调用
- data:传入的数据,传入的数据不能为null,如果没有要传递的数据,也要创建一个空对象
- reply:返回的数据,如果回调未返回数据,可以为空
- flags:额外的标志
- 0:普通的远程通信过程
- FLAG_ONEWAY:单向通信,意味着client的
transact()
单向调用立即返回,而不用等待被叫者的结果。 仅在调用者和被调用者处于不同的进程时适用。
1 | 注意:IBinder的transact()是同步方法,被调用后会一直阻塞直到Binder.onTransact()调用完成后才会返回结果。 |
3. 通过transact()发送的数据是一个Parcel
对象,Parcel
中保存了数据以及描述数据的元数据,元数据在缓存区中保持了 IBinder 对象的引用,这样不同进程都可以访问同一个数据。
元数据用于管理缓冲区中的IBinder对象的引用,确保当IBinder写入Parcel对象发送到另一个进程时,另一个进程发送同一个IBinder回到原来的进程,原进程接收的IBinder对象和开始发送的是同一个引用。
跨进程传输后引用未发生改变,使得IBinder/Binder对象在跨进程通信时可以作为唯一标志(用作token或其他目的)
4. 系统在每个进程中都有一个处理事物的线程池,这些线程用于调度其他进程对当前进程的跨进程访问。
比如说进程 A 对进程 B 发起 IPC 时,A 中调用 transact()
的线程会阻塞。B 中的事物线程池收到 A 的 IPC,调用目标对象的 Binder.onTransact()
方法,然后返回带结果的 Parcel。一旦接收到结果,A 中阻塞的线程得以继续执行。
5. Binder支持进程间的递归调用。
比如,进程 A 向进程 B 发起 IPC,而进程 B 在其 Binder.onTransact()
中又用 transact()
向进程 A 发起 IPC,那么进程 A 在等待它发出的调用返回的同时,也会响应 B 的调用,对调用的对象执行 Binder.onTransact() 方法。
6. 跨进程通信时,IBinder提供三种方法来检测远程进程是否可用
- transact():在其进程不再存在的IBinder上调用它,则transact()方法将引发RemoteException异常。
- pingBinder():如果远程进程不再存在,则会返回false。
- linkToDeath():可以使用linkToDeath()方法向IBinder注册一个DeathRecipient,当其所在的进程消失时将调用它。
1 | 源码: |
2. Binder 代码分析
在AIDL的实践中AIDL自动生成了接口文件,分析一下生成的代码:
生成的接口IUserManager继承自IInterface,并声明了在IUserManager.aidl中声明的方法
根据接口描述分析:
- IInterface:Binder的基类接口,Binder通信中当定义一个新接口,必须继承自IInterface。
IUserManager声明了一个内部类Stub,Stub继承自Binder实现IUserManager,是一个Binder类
Stub内部类实现了IUserManager接口和接口的方法,并定义了几个int类型的整数,整数主要用于标识在transact和onTransact中客户端请求的到底是什么方法。
- Stub.asBinder:返回值IBinder,检索与此接口关联的Binder对象。 您必须使用此方法进行简单转换,以便代理对象可以返回正确的结果。
内部类Stub声明一个内部代理类Proxy
代理类Proxy主要完成方法实现及transact调用过程。
分析: 接口的核心实现是内部类Stub和Stub的内部代理类Proxy,着重分析两个类的方法含义。
DESCRIPTOR
1 | private static final java.lang.String DESCRIPTOR = "com.gqq.binderaidl.IUserManager"; |
Binder的唯一标识,一般用当前Binder的类名表示,类似于Token。
asInterface(android.os.IBinder obj)
1 | /** |
用于将服务端的Binder对象转换为客户端所需要的AIDL接口类型的对象,转换是区分进程的,如果在同一个进程,则返回服务端的Stub本身,否则返回的是系统封装后的Stub.proxy对象,根据queryLocalInterface
可以得知,如果返回null,必须要实例化代理类调用transact(),所以返回Stub.proxy对象。
asBinder
1 |
|
返回当前的Binder对象。
onTransact
1 |
|
方法运行在服务端的Binder线程池中,当客户端发起请求,远程请求会通过底层封装后交给此方法处理,此方法完成后将结果返回给发起请求者。
- 根据code确定客户端请求的是什么目标方法。
- 从data中取出目标方法所需要的参数,如果有参数的话,然后执行目标方法。
- 目标方法执行完毕后,向reply中写入返回值(如果目标方法有返回值的话)
- 如果此方法返回false,客户端请求会失败,可以利用这个特性做权限验证,来避免随便一个进程都可以调用我们的服务。
Proxy#addUser()、getUserList()
两个方法都运行在客户端,大致实现:
- 创建该方法需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象list
- 把该方法的参数信息写入_data中(如果有参数的话)
- 调用transact()方法发起RPC(远程过程调用)请求,同时线程挂起,等待服务端onTransact调用及RPC过程返回,当前线程继续执行,并从reply中取出返回结果,最后返回_replay的数据,没有返回值,就不返回。
注意:客户端发起远程请求时,当前线程会挂起直到服务端进程返回数据,所以如果一个远程方法是耗时的操作,那么不能在UI线程中发起此远程请求。
工作流程图:
创建的aidl文件只是为了帮助生成接口,也可以不借助aid,自己创建接口。
在进程间通讯时两个重要的方法:linkToDeath
和unlinkToDeath
上述有介绍说linkToDeath可以检测服务端进程是否异常,如果断裂,导致远程调用失败,而客户端未知,功能会受影响。为了解决这个问题,所以提供了两个配对的方法:linkToDeath
和unlinkToDeath
,通过linkToDeath
可以给Binder设置一个死亡代理,当Binder死亡时,我们会收到通知。
1 | private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { |
1 | private ServiceConnection connection = new ServiceConnection() { |
也可以通过isBinderAlive判断Binder是否死亡:
1 | userManager.asBinder().isBinderAlive(); |
以上仅针对Java层进行的分析,源码分析原理还需要继续努力!
推荐资料: