传统Linux的IPC通信
传统的IPC通信,由于不同进程间的隔离,用户空间的数据是不能共享的,需要通过内核空间实现数据交换。比如进程A和进程B想要通信,首先进程A将数据通过copy_from_user拷贝到内核空间,然后进程B通过copy_to_user从内核空间拷贝到用户空间。
一次通信需要两次数据拷贝。
Binder通信的好处
BInder通信在内核空间开辟了两块缓存区,一块区域负责数据接收,一块负责数据发送,两块内存区域的地址建立了映射关系,同时在用户空间B建立了一块区域映射到数据接收缓存区。这样当用户空间A通过copy_from_user将数据拷贝到内核空间的时候,用户空间B就接收到了同样的数据。
一次通信只需要一次数据拷贝。
IBinder对象实现数据传输
在本地服务中使用Binder比较简单,只是通过重写Binder的transact()和onTransact()方法实现数据传输。
- 服务端定义一个Binder的子类,在onBind()方法中返回这个Binder对象。
public class BinderService extends Service {
public static final String INTERFACE_NAME = "IReporter";
public static final int CODE_REPORT = 1;
private static final String TAG = "BinderService";
private Reporter mReporter = null;
public BinderService() {
mReporter = new Reporter();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mReporter;
}
public final class Reporter extends Binder {
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
switch (code) {
case CODE_REPORT:
data.enforceInterface(INTERFACE_NAME);
String key = data.readString();
int value = data.readInt();
int result = report(key, value);
reply.writeInterfaceToken(INTERFACE_NAME);
reply.writeInt(result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
public int report(String key, int values) {
Log.d(TAG, "report key=" + key + " value=" + values);
return 0;
}
}
}
- 客户端通过bindService()获取IBinder对象,由IBinder.transact()调用服务端的onTransact()方法,实现数据传输。
实际上传输的数据是Parcel对象,创建Parcel对象的时候,不是通过new方法调用构造函数,而是使用了Parcel.obtain(),这里应该用到了对象的缓存池,类似Message的obtain()方法。Parcel的具体实现大部分是在native层,这里不深入,只是记得最后要调用recycle方法进行对象的回收。
public class BinderClient {
private static final String TAG = "BinderClient";
private IBinder mReporterBinder = null;
public void bindReporter(Context context) {
Intent intent = new Intent(context, BinderService.class);
context.bindService(intent, new BinderConnection(), Context.BIND_AUTO_CREATE);
}
public void report(String key, int value) {
if (mReporterBinder != null) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(BinderService.INTERFACE_NAME);
data.writeString(key);
data.writeInt(value);
try {
mReporterBinder.transact(BinderService.CODE_REPORT, data, reply, 0);
reply.enforceInterface(BinderService.INTERFACE_NAME);
int result = reply.readInt();
Log.d(TAG, "report result=" + result);
} catch (RemoteException e) {
Log.e(TAG, "Fail to transact e=" + e);
} finally {
data.recycle();
reply.recycle();
}
}
}
private class BinderConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected ComponentName=" + name);
mReporterBinder = service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mReporterBinder = null;
}
}
}
这里调用的transact()有两种方式,flag=0和flag=FLAG_ONEWAY。默认情况下,flag=0,客户端线程会阻塞直到服务端返回。
AIDL实现数据传输
Android Interface Definition Language (AIDL)是Android对进程间通信的封装。
Android Studio提供了自动生成aidl接口的方法,只需要新建一个aidl文件,编译项目后就得到一个同名的Java文件。
比如新建一个IReportInterface.aidl文件。
interface IReportInterface {
int report(String key, int values);
}
编译后得到IReportInterface.java,里面有个内部静态抽象类继承了Binder,为我们做好了Parcel数据打包的过程,不需要再手动编码实现。
这里先不看IReportInterface的具体内容。先看下服务端和客户端的实现代码。
服务端的Binder类改成了继承IReportInterface.Stub,不需要再重写onTransact方法,因为aidl已经自动生成了代码。
public class BinderService extends Service {
private static final String TAG = "BinderService";
private Reporter mReporter = null;
public BinderService() {
mReporter = new Reporter();
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return mReporter;
}
public final class Reporter extends IReportInterface.Stub {
public int report(String key, int values) {
Log.d(TAG, "report key=" + key + " value=" + values);
return 0;
}
}
}
客户端也不再需要通过transact方法传输数据,而是通过IBinder对象直接获得Service的实例,直接调用report方法。
public class BinderClient {
private static final String TAG = "BinderClient";
private IReportInterface mReporter = null;
public void bindReporter(Context context) {
Intent intent = new Intent(context, BinderService.class);
context.bindService(intent, new BinderConnection(), Context.BIND_AUTO_CREATE);
}
public void report(String key, int value) {
Log.d(TAG, "report key = " + key + " value = " + value);
if (mReporter != null) {
try {
mReporter.report(key, value);
} catch (RemoteException e) {
Log.e(TAG, "Fail report e=" + e);
}
}
}
private class BinderConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected ComponentName=" + name);
mReporter = IReportInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//
}
}
}
最后再来看下自动生成的代码,是怎么实现数据传输的。
有三个内部类Default,Stub和Proxy。
先看下Stub.asInterface()方法,它的作用是把一个IBinder对象转化成IInterface对象。有三种返回结果,如果IBinder为空,返回null;如果能获取到本地的IInterface对象,返回本地IInterface;兜底方案是新构建一个Proxy对象返回。
实际上如果客户端和服务端在同一个进程,asInterface()方法返回的就是服务端创建的Stub对象,是不需要调用onTransact()方法对Parcel数据进行打包和解包操作的,因为不需要跨进程。
/**
* Cast an IBinder object into an com.one.binder.IReportInterface interface,
* generating a proxy if needed.
*/
public static com.one.binder.IReportInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.one.binder.IReportInterface))) {
return ((com.one.binder.IReportInterface) iin);
}
return new com.one.binder.IReportInterface.Stub.Proxy(obj);
}
只有获取不到Stub对象,才会构造一个Proxy类,重写report()方法,对发送的数据进行打包。
@Override
public int report(java.lang.String key, int values) 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.writeString(key);
_data.writeInt(values);
boolean _status = mRemote.transact(Stub.TRANSACTION_report, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().report(key, values);
}
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
然后在服务端通过onTransact()对数据进行解包,这就是为什么客户端和服务端都需要保存一份AIDL文件。因为它负责数据的打包和解包,双方必须一模一样。
@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_report: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
int _result = this.report(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}