目录
文章参考:
前言
本篇文章主要围绕下面的几个点进行Binder学习的记录。
什么是Binder?
一句话概括:Binder是Android中跨进程通信的一种方式。
为什么跨进程通信要使用binder?
参考:写给 Android 应用工程师的 Binder 原理剖析
文章主要从性能、稳定性、安全性进行对比总结binder的优势所在。
性能方面:
- socket作为一款通用的接口,其传输效率低,开销大。主要用在跨网络的进程间通信和本机上进程间的低速通信。
- 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
- 共享内存虽然无需拷贝,但控制复杂,难以使用
- Binder 只需要一次数据拷贝,性能上仅次于共享内存。
稳定性:
- Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。
- 共享内存虽然无需拷贝,但是在并发读写使它变得极为不可靠。
安全性
传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。
Binder通信模型
摘自 Binder学习指南
对于跨进程通信的双方,我们姑且叫做Server进程(简称Server),Client进程(简称Client);由于进程隔离的存在,它们之间没办法通过简单的方式进行通信,那么Binder机制是如何进行的呢?
回想一下日常生活中我们通信的过程:假设A和B要进行通信,通信的媒介是打电话(A是Client,B是Server);A要给B打电话,必须知道B的号码,这个号码怎么获取呢?通信录.
这个通信录就是一张表;内容大致是:
1 2
B -> 12345676 C -> 12334354
先查阅通信录,拿到B的号码;才能进行通信;否则,怎么知道应该拨什么号码?回想一下古老的电话机,如果A要给B打电话,必须先连接通话中心,说明给我接通B的电话;这时候通话中心帮他呼叫B;连接建立,就完成了通信。
另外,光有电话和通信录是不可能完成通信的,没有基站支持;信息根本无法传达。
我们看到,一次电话通信的过程除了通信的双方还有两个隐藏角色:通信录和基站。Binder通信机制也是一样:两个运行在用户空间的进程要完成通信,必须借助内核的帮助,这个运行在内核里面的程序叫做Binder驱动,它的功能类似于基站;通信录呢,就是一个叫做ServiceManager的东西(简称SM)
OK,Binder的通信模型就是这么简单,如下图:
Binder通信原理
摘自 Binder学习指南
上文给出了Binder的通信模型,指出了通信过程的四个角色: Client, Server, SM, driver; 但是我们仍然不清楚Client到底是如何与Server完成通信的。
两个运行在用户空间的进程A和进程B如何完成通信呢?内核可以访问A和B的所有数据;所以,最简单的方式是通过内核做中转;假设进程A要给进程B发送数据,那么就先把A的数据copy到内核空间,然后把内核空间对应的数据copy到B就完成了;用户空间要操作内核空间,需要通过系统调用;刚好,这里就有两个系统调用:
copy_from_user
,copy_to_user
。但是,Binder机制并不是这么干的。讲这么一段,是说明进程间通信并不是什么神秘的东西。那么,Binder机制是如何实现跨进程通信的呢?
Binder驱动为我们做了一切。
假设Client进程想要调用Server进程的
object
对象的一个方法add
;对于这个跨进程通信过程,我们来看看Binder机制是如何做的。 (通信是一个广泛的概念,只要一个进程能调用另外一个进程里面某对象的方法,那么具体要完成什么通信内容就很容易了。)首先,Server进程要向SM注册;告诉自己是谁,自己有什么能力;在这个场景就是Server告诉SM,它叫
zhangsan
,它有一个object
对象,可以执行add
操作;于是SM建立了一张表:zhangsan
这个名字对应进程Server;然后Client向SM查询:我需要联系一个名字叫做
zhangsan
的进程里面的object
对象;这时候关键来了:进程之间通信的数据都会经过运行在内核空间里面的驱动,驱动在数据流过的时候做了一点手脚,它并不会给Client进程返回一个真正的object
对象,而是返回一个看起来跟object
一模一样的代理对象objectProxy
,这个objectProxy
也有一个add
方法,但是这个add
方法没有Server进程里面object
对象的add
方法那个能力;objectProxy
的add
只是一个傀儡,它唯一做的事情就是把参数包装然后交给驱动。(这里我们简化了SM的流程,见下文)但是Client进程并不知道驱动返回给它的对象动过手脚,毕竟伪装的太像了,如假包换。Client开开心心地拿着
objectProxy
对象然后调用add
方法;我们说过,这个add
什么也不做,直接把参数做一些包装然后直接转发给Binder驱动。驱动收到这个消息,发现是这个
objectProxy
;一查表就明白了:我之前用objectProxy
替换了object
发送给Client了,它真正应该要访问的是object
对象的add
方法;于是Binder驱动通知Server进程,调用你的object对象的add
方法,然后把结果发给我,Sever进程收到这个消息,照做之后将结果返回驱动,驱动然后把结果返回给Client
进程;于是整个过程就完成了。由于驱动返回的
objectProxy
与Server进程里面原始的object
是如此相似,给人感觉好像是直接把Server进程里面的对象object传递到了Client进程;因此,我们可以说Binder对象是可以进行跨进程传递的对象但事实上我们知道,Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。
理解这一点非常重要;务必仔细体会。另外,Android系统实现这种机制使用的是代理模式, 对于Binder的访问,如果是在同一个进程(不需要跨进程),那么直接返回原始的Binder实体;如果在不同进程,那么就给他一个代理对象(影子);我们在系统源码以及AIDL的生成代码里面可以看到很多这种实现。
另外我们为了简化整个流程,隐藏了SM这一部分驱动进行的操作;实际上,由于SM与Server通常不在一个进程,Server进程向SM注册的过程也是跨进程通信,驱动也会对这个过程进行暗箱操作:SM中存在的Server端的对象实际上也是代理对象,后面Client向SM查询的时候,驱动会给Client返回另外一个代理对象。Sever进程的本地对象仅有一个,其他进程所拥有的全部都是它的代理。
一句话总结就是:Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。
从AIDL的角度来理解Binder
binder学习指南中举了定义了一个简单的aidl接口ICompute
// ICompute.aidl
package com.example.test.app;
interface ICompute {
int add(int a, int b);
}
经过编译生成的java代码类结构如下:
Binder/IBinder: 它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递。也可以将binder理解成一种协议,类似http在 客户端 发起http请求,服务端接收到请求报文,做对应的处理将结果返回给客户端。在跨进程通信的过程中binder就是网络通信中的http
IInterface: IInterface代表的就是远程server对象具有什么能力,具体来说,就是aidl里面的接口。同时指导如何获取跨进程传输的binder。
这样来看的话,Stub继承Binder实现了IInterface接口,既可以进行跨进程传输,同时可以直接和远端进行交互。
Proxy:实现了IInterface接口通过它可以获取到跨进程传输的binder
AIDL java代码分析
aidl生成的java代码如下:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.txl.tool;
// Declare any non-default types here with import statements
public interface ICompute extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.txl.tool.ICompute {
private static final java.lang.String DESCRIPTOR = "com.example.txl.tool.ICompute";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface( this, DESCRIPTOR );
}
/**
* Cast an IBinder object into an com.example.txl.tool.ICompute interface,
* generating a proxy if needed.
*/
public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
return ((com.example.txl.tool.ICompute) iin);
}
return new com.example.txl.tool.ICompute.Stub.Proxy( obj );
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString( descriptor );
return true;
}
case TRANSACTION_add: {
data.enforceInterface( descriptor );
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add( _arg0, _arg1 );
reply.writeNoException();
reply.writeInt( _result );
return true;
}
default: {
return super.onTransact( code, data, reply, flags );
}
}
}
private static class Proxy implements com.example.txl.tool.ICompute {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken( DESCRIPTOR );
_data.writeInt( a );
_data.writeInt( b );
mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}
这里的java代码符合前面对Binder和IInterface的描述。
Server端实现的时候继承Stub类,它能够进行跨进程传输供Client端调用。
Client端调用Stub的静态 方法asInterface获取一个具有服务端外观(和服务端有同样的方法)的ICompute对象。
其实asInterface方法和Stub方法没有什么太大的关系。它的代码如下:
public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
return ((com.example.txl.tool.ICompute) iin);
}
return new com.example.txl.tool.ICompute.Stub.Proxy( obj );
}
逻辑是:先从本地去查看是否有符合需求的binder对象,如果有直接返回本地对象。如果没有生成一个Proxy对象返回。
对于客户端而言这个Proxy类与其说是一个代理对象倒不如说是它在指导Binder如何进行数据处理。我们看看它的add方法
@Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken( DESCRIPTOR );
_data.writeInt( a );
_data.writeInt( b );
//除了这句代码,其他的全部在进行数据处理
mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到当调用add方法的时候除了 mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );其他的都在进行数据处理。而transact就像是将指定数据同步发送到服务端然后等待响应。这也是前面我为什么将Binder类比成http。
客户端 transact方法接收4个参数,他们分别是
- 要调用方法的编码
- 要发送给服务端参数的容器
- 要返回数据的容器
- 标记falg(暂时不知道这个参数的作用)
服务端onTransact同样的接收4个参数,他们分别与客户端的4个参数对应。
这样一次aidl跨进程通信就分析完了。做一个简单的总结:
服务端:实现继承Binder并实现IIterface,重写onTransact方法处理客户端的回调。
客户端:同过绑定服务时得到的Ibinder对象,通过transact进行对应的数据传输。
demo:手写最简单的aidl
服务端实现:
package com.txl.calculationserver;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;
public class CalculationService extends Service {
private static final String TAG = "CalculationService";
Binder binder = new CalculationBinder();
public CalculationService() {
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.d( TAG,"onCreate" );
}
private static class CalculationBinder extends Binder implements IInterface {
private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;
public CalculationBinder() {
this.attachInterface( this, DESCRIPTOR );
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString( descriptor );
return true;
}
case TRANSACTION_add: {
data.enforceInterface( descriptor );
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add( _arg0, _arg1 );
reply.writeNoException();
reply.writeInt( _result );
return true;
}
default: {
return super.onTransact( code, data, reply, flags );
}
}
}
public int add(int a, int b) throws RemoteException {
Log.d( TAG,"call add method by client" );
return a+b;
}
}
}
客户端实现:
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private ProxyCalculationServer proxyCalculationServer;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
proxyCalculationServer = ProxyCalculationServer.asInterface( iBinder );
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
findViewById( R.id.tv_call_remote_add ).setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View view) {
if(proxyCalculationServer != null){
try {
int result = proxyCalculationServer.add( 3,6 );
Log.d( TAG,"result = "+result );
} catch (RemoteException e) {
e.printStackTrace();
}
}else {
Log.e( TAG,"proxyCalculationServer is null" );
}
}
} );
Intent intent = new Intent( );
intent.setAction( "com.txl.calculationserver" );
intent.setPackage( "com.txl.calculationserver" );
bindService( intent,connection,BIND_AUTO_CREATE );
}
static class ProxyCalculationServer implements IInterface {
private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;
private android.os.IBinder mRemote;
public ProxyCalculationServer(IBinder obj) {
mRemote = obj;
}
// @Override
// public android.os.IBinder asBinder() {
// return mRemote;
// }
public static ProxyCalculationServer asInterface(IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
if (((iin != null) && (iin instanceof ProxyCalculationServer))) {
return ((ProxyCalculationServer) iin);
}
return new ProxyCalculationServer( obj );
}
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken( DESCRIPTOR );
_data.writeInt( a );
_data.writeInt( b );
mRemote.transact( TRANSACTION_add, _data, _reply, 0 );
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
}
这个两个文件分别实现在不同的项目中。对于简单的xml没有贴上代码。
最后服务端和客户端的运行结果如下
通过上面的案例可以看到,对于服务端而言不是一定要获取一个Proxy对象。只要你懂得binder传输过程中的数据处理,甚至完全可以直接自己调用transact。
总结:
aidl实现跨进程通信不是必须要先编写 .aidl 文件完全可以按照前面总结aidl需要完成的东西来进行编写。但是aidl文件帮助我们更加简单的实现款进程通信的过程,也不容易出错。建议大家在实现aidl的时候不要手写,手写实现过程的目的是为了更好的了解这一过程。
问题
- 服务端什么时候被注册到ServerManager?
- IInterface接口的asBinder方法是干什么的?为什么返回null也能够正常调用?