一、什么是Binder
Binder在Android中的应用场景非常的多,以至于应用不同的应用场景来解释Binder,会更加通俗易懂。
1)从应用机制来讲,Binder是Android一种实现跨进程通信(IPC)的方式。
2)从模型结构来讲,Binder是一种虚拟的物理设备驱动,连接server进程、client进程和ServiceManager进程
3)从实现来讲,Binder是一个类,实现了IBinder接口,将Binder机制以代码的形式,具体体现在Android中
二、关于Linux的一些基础知识
1.进程空间
因为Android内核是Linux的,所以在进程通信管理等方面,我们有必要先去了解一些关于Linux的基础知识。
1)进程空间划分
一个进程空间分为用户空间和内核空间,即把进程内用户与内核分割开来,因为在Linux中,进程间用户空间数据不可共享,所以用户空间 = 不可共享空间;进程间内核空间的数据可共享,所以内核空间 = 可共享空间
所有进程共享一个内核空间
进程内用户空间和内核空间进行交互需要通过系统调用
2)进程隔离和跨进程通信(IPC)
进程隔离:为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的
传统的Linux系统如果需要进行通信,发送进程需要通过系统调用,将需要发送的数据拷贝到Linux进程的内核空间中的缓存区(数据拷贝第一次,通过copy_from_user()),接着内核服务程序唤醒接收进程的接收线程,通过系统调用将数据发送到接收进程的用户空间中,最终完成数据发送(数据拷贝第二次、通过copy_to_user()),通过这样的方式,实现了进程间的数据交换
但是这样做会有效率低下的缺点,因为要拷贝两次数据,而且接收数据的缓存是需要有接收方提供,但接收方却不知道到底要多大的缓存,一般的做法是开辟尽量大的空间或者先调用api获取数据的大小,不过前者浪费空间,后者浪费时间。
而Binder的作用是:连接两个进程,实现了mmap()系统调用,主要负责创建数据接收的缓存空间和管理数据接收缓存。传统的跨进程通信需要拷贝数据两次,但Binder机制只需要1次,主要是使用了内存映射。
2.内存映射
内存映射即关联进程中的一个虚拟内存区域或者一个磁盘上的对象,使得两者存在映射关系。首先初始化该虚拟内存区域,虚拟内存区域被初始化后,就会在交换空间中进程数据交换,被映射的对象称为共享对象。
若存在上述映射关系,则有以下特征
- 多个进程的虚拟内存区域已和同一个共享对象建立映射关系
- 如果其中1个进程对该虚拟区进行写操作,那么和该虚拟区域建立映射关系的其他进程也是可见的
通过内存映射,提高了数据进程间传输的性能
- 减少了数据的拷贝次数
- 用户空间和内核空间的高效传输(通过映射区域直接交互)
- 用内存读写代替I/O读写
三、Binder原理
Binder跨进程通信机制基于Client - Server模式
Client进程:即Android客户端,使用服务的进程
Server进程:即服务端,提供服务的进程
ServiceManager:管理Service的注册与查询(将字符形式的Binder名字转化成Client中对该Binder的引用)
Binder驱动:一种虚拟设备驱动,是连接Sever进程、Client进程和ServiceManager的桥梁,具体作用有
- 传递服务进程的消息
- 传递进程间需要传递的数据:通过内存映射
- 实现线程控制:采用Binder线程池,并由Binder驱动自身进行管理
Binder驱动持有每个sever进程在内核空间中Binder实体,并给Client进程提供Binder实体的引用
工作流程:
1)Binder驱动创建一块接收缓存区
2)实现地址映射关系:即根据需映射的接收进程信息,实现内核缓存区和接收进程用户空间地址同时映射到一个共享缓存区中
上面两个步骤仅仅建立了映射关系,并没有数据的传送,真正数据传送的实际是在进程发起读写操作时
3)发送进程通过系统调用copy_from_user()发送数据到虚拟内存区域(数据拷贝1次)
4)由于内核缓存区和接收进程的用户空间地址存在映射关系,故相当于也发送到了接收进程的用户空间地址,即实现了跨进程通信。
四、Binder机制在Android中具体实现
Binder机制在Android中实现主要依靠Binder类,其实现了IBinder接口。
举一个简单的例子来进行说明:Client发送一个String给服务端,服务端收到处理后在返回给客户端。
1)建立服务端
public class BinderService extends Service {
private Binder mBinder = new Binder() {
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == 1) {
String name = data.readString();
String result = "Hello " + name + ", server handle success!";
assert reply != null;
reply.writeString(result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
在onTransact方法中,接收到客户端传来的参数,做一步处理,在返回给客户端。
服务要在清单文件中配置,并且设置为不同进程
<service
android:name=".binder.BinderService"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.bind.binderservice" />
</intent-filter>
</service>
2)建立客户端
客户端主要工作有两步
第一步是绑定服务:
String action = "android.intent.action.bind.binderservice";
Intent intent = new Intent(action);
intent.setPackage("com.android.study");
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
private IBinder mRemote = null;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mRemote = service;
Toast.makeText(BinderActivity.this, "绑定成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemote = null;
Toast.makeText(BinderActivity.this, "远程服务链接已断", Toast.LENGTH_SHORT).show();
}
};
第二步是调用transact方法交互
mRemote.transact(1, data, reply, 0);
完整Activity代码如下,第一个btn点击后绑定服务,第二个btn点击后进行通信
public class BinderActivity extends AppCompatActivity {
private IBinder mRemote = null;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mRemote = service;
Toast.makeText(BinderActivity.this, "绑定成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemote = null;
Toast.makeText(BinderActivity.this, "远程服务链接已断", Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder);
findViewById(R.id.bind_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String action = "android.intent.action.bind.binderservice";
Intent intent = new Intent(action);
intent.setPackage("com.android.study");
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
});
findViewById(R.id.service_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String result = null;
try {
result = getResult("test");
} catch (RemoteException e) {
e.printStackTrace();
}
((AppCompatButton)findViewById(R.id.service_btn)).setText(result);
}
});
}
private String getResult(String name) throws RemoteException {
android.os.Parcel data = android.os.Parcel.obtain();
android.os.Parcel reply = android.os.Parcel.obtain();
String result;
try {
data.writeString(name);
mRemote.transact(1, data, reply, 0);
result = reply.readString();
} finally {
reply.recycle();
data.recycle();
}
return result;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bind_btn"
android:text="click to bind service"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:gravity="center"
android:layout_height="48dp"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/service_btn"
android:layout_width="match_parent"
android:layout_gravity="center"
android:text="click to ipc"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:gravity="center"
android:layout_height="48dp"/>
</LinearLayout>