安卓移动架构07-Binder核心机制
什么是Binder
- 从机制上说,Binder是一种Android中实现跨进程通信(IPC)的方式;
- 从设备上说,Binder是一种虚拟的物理设备驱动;
- 从代码上说,BInder 是IBinder接口的实现类,将Binder机制模型以代码的形式 实现在整个Android系统中。
为什么要使用Binder
Android系统底层是Linux系统,Linux系统原本就具有IPC方式,但是Andriod为什么不使用Linux的IPC方式,而要设计Binder机制呢?
Linux的IPC方式主要有8种:
- 管道 (PIPE),实际是用于进程间通信的一段共享内存。缺点:需要将一块内存拷贝两次,第一次拷贝到共享区,第二次拷贝到目标进程,效率低。
- 命名管道(FIFO),是一种特殊类型的文件。缺点:是基于文件的io操作,性能差。
- 信号 (signal),是一种以事件为驱动的通信方式。缺点:不适合信息的传递。
- 消息队列(Message queues),是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容。缺点:跟管道一样。
- 信号量(Semaphore),是一种计数器,用于控制对多个进程共享的资源进行的访问。缺点:跟信号一样。
- 共享内存(Share Memory),是在多个进程之间共享内存区域的一种进程间的通信方式。缺点:需要自己实现进程间的同步。
- 内存映射(Memory Map),是由一个文件到一块内存的映射。缺点:需要自己实现进程间的同步。
- 套接字 (Socket),是一种跨网络的通信方式。缺点:传输效率低,开销大。
Binder相比传统的IPC方式,主要有两个优点:
- 效率高。Binder采用内存共享的机制,但是只要进行一次内存拷贝。
- 更安全。Linux的IPC方式无法获取目标进程的UID/PID,无法鉴别身份;Android可以获取目标进程的进程的UID/PID,从而控制访问权限。
Binder的使用
Binder依赖于Service,在组件(Activty)中通过bindService(),就可以获取Service中的Binder对象,实现Service与Activity的通信。
服务分为本地服务和远程服务,都可以使用Binder。
1 本地服务
在本地服务中使用Binder,只需要两步:
- 声明一个Service,重写onBind(),返回一个继承Binder的自定义对象;
- 声明一个ServiceConnection,重写onServiceConnected(),获取IBinder对象,然后调用Activity的bindService();
声明Service:
public class Service2 extends Service {
String TAG = "TestService";
MyBinder myBinder = new MyBinder();
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind() executed");
return myBinder;
}
//定义一个类实现IBinder接口(Binder实现了IBinder接口)
class MyBinder extends Binder {
public void doSomething() {
Log.d("TAG", "doSomething() executed");
}
}
}
在Activity中绑定服务:
private void myBindService() {
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (Service2.MyBinder) service;
myBinder.doSomething();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected() executed");
}
};
Intent bindIntent = new Intent(this, Service2.class);
//此时使用bindService开启服务
bindService(bindIntent, connection, BIND_AUTO_CREATE);
//销毁服务
unbindService(connection);
}
2 远程服务
在远程服务中使用Binder,需要三步:创建aidl、声明Service、调用bindService。
2.1 创建aidl
aidl是进程间通信接口,需要在客户端(声明Service的应用)和服务端(调用Service的应用)同时声明,并且要完全一致。
首先,在服务端端创建aidl:选中项目目录->右键选中new->然后选中AIDL->填写文件名(比如:TestAidl)->修改aidl的代码。
// TestAidl.aidl
package gsw.demopluggable;
// Declare any non-default types here with import statements
interface TestAidl {
String getName();
String setName(String name);
}
然后,在客户端创建aidl,步骤同上。客户端的aidl要与服务端的一模一样,包括包名、类名、方法。
然后,选中Build->Make Project,编译整个工程,就会在build/generated/source/aidl/debug目录下生产一个名为TestAidl的接口。
2.2 声明Service
在服务端,声明一个类(比如:MyStub)继承TestAidl.Stub(TestAidl的代理类,系统帮我们生成的),然后声明一个继承Service的类,在onBind()中返回MyStub对象。
public class ServiceBinder extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyStub();
}
public static class MyStub extends TestAidl.Stub {
String name = null;
@Override
public String getName() throws RemoteException {
return name;
}
@Override
public String setName(String name) throws RemoteException {
this.name = name;
return name;
}
}
}
2.3 调用bindService
在客户端,声明一个匿名内部类ServiceConnection,重写onServiceConnected(),通过TestAidl.Stub.asInterface(iBinder)获取服务端的TestAidl对象。
然后,通过远程服务的action调用bindService()。
/**
* 绑定远程服务
* action为"gsw.demopluggable2.binder.ServiceBinder"
*/
private void bindRemoteServer() {
Intent intent = new Intent();
intent.setAction("gsw.demopluggable2.binder.ServiceBinder");
intent.setPackage("gsw.demopluggable2");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
TestAidl testAidl = TestAidl.Stub.asInterface(iBinder);
try {
testAidl.setName("David");
Toast.makeText(ActivityBinder.this, "---> " + testAidl.getName(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}, Context.BIND_AUTO_CREATE);
}
3 使用自定义类型
aidl中,使用自定义类型,需要在创建一个对应的aidl文件来声明这个类型,并且这个类必须为Parcelable类型。
首先,创建Java类,实现Parcelable接口。
package gsw.demopluggable.binder;
import android.os.Parcel;
import android.os.Parcelable;
public class Item1 implements Parcelable {
public String name;
public int id;
protected Item1(Parcel in) {
name = in.readString();
id = in.readInt();
}
public Item1(String name, int id) {
this.name = name;
this.id = id;
}
public static final Creator CREATOR = new Creator() {
@Override
public Item1 createFromParcel(Parcel in) {
return new Item1(in);
}
@Override
public Item1[] newArray(int size) {
return new Item1[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(id);
}
}
然后,创建一个对应的aidl文件:Item1.adil。它们两个的包名必须相同,且必须服务端的包名相同。
// Item1.aidl
package gsw.demopluggable.binder;
//声明是Parcelable类型,作为参数使用
parcelable Item1;
然后,就可以在其它的aidl中引用Item1.adil。
// TestAidl.aidl
package gsw.demopluggable;
//import自定义类型
import gsw.demopluggable.binder.Item1;
import gsw.demopluggable.binder.Item2;
// Declare any non-default types here with import statements
interface TestAidl {
String getName();
String setName(String name);
//Item1作为参数时,需要在前面加in,代表输入类型
Item2 getItem2(in Item1 item1);
}
Binder的原理
1 内核共享
Binder是通过共享内存的方式,实现进程间通信的。
那么,共享的是哪一块内存呢?我们看下面这张图:
[图片上传失败...(image-b9913e-1564362909580)]
每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。
Client进程向Server进程通信,就是获取Server进程的Binder对象,只需要从Server进程的用户空间拷贝一份内存到Client进程的用户空间。
2 通信流程
通信流程可以分为3步:注册服务、获取服务、使用服务。
[图片上传失败...(image-c63380-1564362909581)]
首先需要注册服务,服务端通过 ServiceManager 注册服务。注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。
然后是获取服务端。获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。
最后是使用服务。使用服务就是通过Binder驱动将服务端的Binder对象,拷贝到客户端,然后通过Binder对象进行通讯。
注意:客户端、服务端、ServiceManager都在不同的进程,它们之间都是通过Binder机制进行通讯的。
3 aidl机制
上面的通信流程好像跟aidl没什么关系,但是实际使用时却不能少,这是为什么呢?
aidl是进程间通信接口,它不是Java的类,更像一种协议。它的作用是让AS自动生成Binder类,供客户端调用。
aidl文件编译后,会生成一个Java接口,分为3层:TestAidl、Stub、Proxy。
package gsw.demopluggable;
public interface TestAidl extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements gsw.demopluggable.TestAidl {
...
}
private static class Proxy implements gsw.demopluggable.TestAidl {
...
}
public java.lang.String getName() throws android.os.RemoteException;
public java.lang.String setName(java.lang.String name) throws android.os.RemoteException;
}
3.1 TestAidl
TestAidl就是aidl文件中声明的接口,也就是我们要给客户端调用的功能。
3.2 Proxy
Proxy,顾名思义,就是TestAidl的代理类,用来实现TestAidl的方法。Proxy的作用是将需要传递的参数转化为Parcel,从而跨进程传输。
看下面的例子:
public java.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
//用来标识_data是给含有DESCRIPTOR标志的Binder接口的参数
_data.writeInterfaceToken(DESCRIPTOR);
//调用Stub的transact,处理getName请求
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
//获取Stub的处理结果
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
3.3 Stub
Stub是一个使用TestAidl装饰的Binder类,是我们实际传递给客户端的对象。通过Stub,客户端就可以调用TestAidl的方法。
首先是DESCRIPTOR,默认值是包名+类名,作用是在IBinder中查找TestAidl接口。
private static final java.lang.String DESCRIPTOR = "gsw.demopluggable.TestAidl";
然后是asInterface(),作用是返回Proxy对象,也就是把TsetAidl对象返回给客户端。
public static gsw.demopluggable.TestAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof gsw.demopluggable.TestAidl))) {
return ((gsw.demopluggable.TestAidl) iin);
}
return new gsw.demopluggable.TestAidl.Stub.Proxy(obj);
}
然后是onTransact(),作用是处理客户端的请求,并将处理结果返回给客户端。
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
//用来指定DESCRIPTOR,从而确定需要通讯的接口
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
//用来处理TestAidl的getName()
case TRANSACTION_getName: {
//说明data正准备给指定DESCRIPTOR的接口使用
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
//说明当前操作没有出现异常
reply.writeNoException();
reply.writeString(_result);
return true;
}
//用来处理TestAidl的setName()
case TRANSACTION_setName: {
//说明data正准备给指定DESCRIPTOR的接口使用
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _result = this.setName(_arg0);
//说明当前操作没有出现异常
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
最后
代码:https://gitee.com/yanhuo2008/AndroidCommon/tree/master/DemoPluggable
参考文章:
https://blog.csdn.net/a987073381/article/details/52006729
https://blog.csdn.net/freekiteyu/article/details/70082302