1、Binder机制概述
Binder是Android系统中用于不同进程间通信(IPC)的一种高效机制。它基于Client-Server架构,允许客户端(如Activity、Service等)通过Binder代理对象与服务器端进行通信。在通信过程中,数据只需从发送方进程的用户空间拷贝到内核空间一次,然后由接收方进程直接从内核空间访问,从而减少了数据拷贝的次数,提高了通信效率。
2、主要组件
- Client(客户端):发起IPC请求的进程。
- Server(服务器端):提供服务的进程,并向ServiceManager注册服务,以便客户端查询。
- ServiceManager(服务管理器):系统级服务,负责维护Binder服务的列表,供客户端查询服务。
- Binder驱动程序:位于内核空间的组件,处理IPC的底层通信。它通过/dev/binder设备节点与用户空间交互,并开辟内核缓存区与接收方进程的用户空间建立地址映射。
3、完整通信过程
以电话APP调起录音机服务为例:
- 发送方(电话APP):通过系统调用(如copy_from_user())将数据和目标Binder引用发送给Binder驱动,完成数据从用户空间到内核空间的唯一一次拷贝。
- 接收方(录音机服务):由于Binder驱动已开辟的内核缓存区与接收方用户空间建立了映射(通过mmap()),录音机服务可以直接访问这些数据,无需再次拷贝,从而实现了高效的IPC通信。
4、工作原理
- 服务注册:Server端通过Binder驱动向系统注册服务。
- 服务查询:Client端通过ServiceManager查询所需服务,并获取对应的Binder引用。
- 代理对象创建:Client端使用获取的Binder引用创建代理对象,该对象封装了跨进程通信的细节。
- 消息传递:Client端通过代理对象调用Server端的方法,Binder驱动处理跨进程通信的具体细节。
5、优点
- 高效性:通过减少数据拷贝次数和共享内存的方式提高IPC效率。
- 安全性:支持通信双方的身份校验,增强系统安全性。
- 面向对象:支持面向对象的调用方式,使得跨进程通信更加直观和易于管理。
6、AIDL(Android Interface Definition Language)使用
AIDL是Android中用于定义跨进程通信接口的语言,它能够自动生成Binder通信所需的代码。
步骤
-
定义AIDL接口:
- 创建一个
.aidl
文件,在其中定义接口和方法。 - 将该文件放置在
src/main/aidl/<package_name>/
目录下。
- 创建一个
-
实现AIDL接口:
- 在Service中创建一个继承自
IMyService.Stub
的内部类,并重写定义在AIDL接口中的方法。 - 在
onBind
方法中返回该内部类的实例作为Binder对象。
- 在Service中创建一个继承自
-
客户端绑定服务:
- 将AIDL文件复制到客户端项目中。
- 使用
bindService
方法绑定服务,并通过ServiceConnection
回调获取Binder代理对象。 - 通过该代理对象调用服务端定义的方法。
示例
AIDL接口定义(IMyService.aidl)
package com.example.myapp;
interface IMyService {
void doSomething(int value);
int getValue();
}
服务端实现(MyService.java)
public class MyService extends Service {
private final IMyService.Stub mBinder = new IMyService.Stub() {
@Override
public void doSomething(int value) throws RemoteException {
// 实现方法体
}
@Override
public int getValue() throws RemoteException {
return 0; // 返回示例值
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
客户端调用(MyClientActivity.java)
public class MyClientActivity extends Activity {
private IMyService mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mService = IMyService.Stub.asInterface(service);
try {
mService.doSomething(123);
int value = mService.getValue();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_client);
Intent intent = new Intent(this, MyService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
注意事项
- 确保Service在AndroidManifest.xml中正确注册。
- 如果需要跨应用通信,请确保设置
android:exported="true"
。 - 处理
RemoteException
异常,以应对远程服务不可用时的情况。 - 自定义数据类型需实现
Parcelable
接口,以便在IPC过程中进行序列化与反序列化。
7、为什么是接收方和内核空间做映射,不是发送方做映射
这主要基于以下几个原因:
- 减少拷贝次数:如果发送方与内核空间进行映射,那么数据仍然需要从发送方的用户空间拷贝到内核空间,然后再从内核空间拷贝到接收方的用户空间(即使是通过共享内存)。而通过让接收方与内核空间进行映射,发送方只需将数据拷贝到共享内存区域(即内核空间中的映射区域),接收方即可直接通过映射访问这些数据,从而实现了真正意义上的一次拷贝。
- 灵活性和可扩展性:接收方与内核空间进行映射使得系统能够更灵活地处理多个接收方的情况。当有新的接收方加入时,只需为该接收方建立与内核空间的映射关系即可,而无需修改发送方的逻辑。
- 安全性考虑:通过控制接收方对共享内存的访问权限,系统可以更有效地保障数据的安全性。如果发送方与内核空间进行映射,则可能需要更复杂的安全机制来防止发送方对共享内存的非法访问。