一、Binder框架
Binder用于完成进程间通信(IPC),比如普通应用程可以调用音乐服务,它工作在内核态,属于一个驱动,只是这个驱动要用的“硬件”是内存。
Binder架构由三个模块构成:服务端接口,Binder驱动,客户端接口。我们分开来看:
- 服务端
一个Binber服务端实际上是一个Binder类的对象,且一旦创建,内部就启动一个隐藏线程,用来接收Binder驱动发送的消息,收到消息后,会执行Binder对象中的onTransact()方法,并执照不同的参数执行不同的服务代码,所以实现一个Binder对象一定要重载onTransact()方法,onTransact()方法的参数来自于客户端调用transact()方法时的输入,两者的格式和顺序一定要一样。
- Binder驱动
当服务端Binder对象创建时,同时会在Binder驱动中创建一个mRemote对象(这里不会再有新的线程),mRemote也是Binder类。当客户端访问远程服务时,是通过 mRemote对象 。
- 客户端
要访问服务端,先得到远程对象对应的mRemote对象,然后就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容重要是:
1.以线程间通信的模式,向服务端发送客户端传递过来的参数。
2.挂起当前线程(客户端线程),并等待服务端后的通知(notify)。
3.接收到服务端线程的能通知,然后继续执行客户端线程。
二、Server端的设计
只要基于Binder类新建一个Server类就可以了,如下
public class MusicPlayerService extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
return super.onTransact(code, data, reply, flags);
}
public void start(String filePath){
}
public void stop (){
}
}
要启动服务,只要初始化一个MusicPlayerService就可,如要Activity中初始化,会在ddms中看到多了一个线程。
定义了服务类后,就要重载onTransact()方法,并从data中读取出客户端发送来的参数,比如start()中的filePath,这里有一个服务端与客户端约定的问题。
假如客户端传入的第一个参数是filePath,则
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//code表示想调用服务端的那个方法
switch (code) {
case 1000:
//一种校验
data.enforceInterface("MusicPlayerService");
String filePath = data.readString();
start(filePath);
break;
}
return super.onTransact(code, data, reply, flags);
}
如果IPC调用希望返回一 些结果,可以在返回的paracel reply中写入结果。
三、Binder客户端设计
要想使用服务端,先要得到服务端在Binder驱动中对应的mRemote引用(怎么得到见后面),然后再调用mRemote变量的transact()方法。原型如下
public final boolean transact(int code, Parcel data, Parcel reply, int flags)
调用这个方法后,客户端线程进入Binder驱动,Binder驱动会挂起客户端线程,并向远程服务发送一个消息,消息中有客户端传递的参数,服务端执行完后,向Binder驱动发送一个notify消息,从而使客户端线程从Binder驱动代码区返回到客户端代码区。最后客户端就可以从reply中解析返回的数据了。
四、使用Service类
手写Binder服务端和客户端的过程有两个问题:
第一,客户端如何得到服务端的Binder引用(使用Service)
第二,客户端与服务端一定要事先约好两件事情(用aidl工具)
(1)服务端函数的参数的顺序。
(2)服务端不同的函数的int标识。
使用Binder是想提一个全局的服务,也就是在系统中的任何地方都可以访问。我们还有一个更傻瓜的方法:Service .
- 如何得到Binder对象
对于高手 ,完全可以不使用Service类,而只要使用Binder编写服务程序,但只是一部分。因为可以基于Binder类扩展系统服务,而客户端服务则一定要基于Service.这里的系统服务是指可以使用getSystemService()得到的服务,客户端服务是指app自定义的服务。
AmS提供了startService()来启动客户服务,对于客户端,可以使用下面的两个方法来与一个服务建立连接:
public Component startService(Intent intent )
这里只是启动了intent指定的服务,还没有得到服务端的Binder引用,所以还不能调用服务端的功能。
public boolean bindService (Intent service, ServiceConnection conn, int flags)
可以用于绑定一个服务,这是第一个问题的关键所在,里面的第二个参数是一个interface.
public interface ServiceConnection {
/**
* Called when a connection to the Service has been established, with
* the {@link android.os.IBinder} of the communication channel to the
* Service.
*
* @param name The concrete component name of the service that has
* been connected.
*
* @param service The IBinder of the Service's communication channel,
* which you can now make calls on.
*/
public void onServiceConnected(ComponentName name, IBinder service);
public void onServiceDisconnected(ComponentName name);
}
在客户端中,可以在onServiceConnected()方法中将参数service保存为一个全局变量,从而在客户端的任何地方都可以调用远程服务。
五、例子
工程目录如
先创建Fruit类
/**
* Created by lsj on 2015/9/15.
*/
public class Fruit implements Parcelable{
private String name ;
private String color ;
private int number ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public static final Creator<Fruit> CREATOR = new Creator<Fruit>() {
@Override
public Fruit createFromParcel(Parcel source) {
Fruit fruit = new Fruit();
fruit.name = source.readString() ;
fruit.color = source.readString();
fruit.number= source.readInt() ;
return fruit;
}
@Override
public Fruit[] newArray(int size) {
return new Fruit[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(color);
dest.writeInt(number);
}
}
Fruit.aidl如下
IFruitBinder.aidl如下
在点击build-->make project后,会在如下的目录中生成代码 IFuitBinder.java
分析如下
Service类如下
public class FruitAidlService extends Service {
private Fruit mFruit ;
@Override
public void onCreate() {
super.onCreate();
mFruit = new Fruit();
mFruit.setName("apple");
mFruit.setColor("red");
mFruit.setNumber(10);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
/**
* 分析IFruitBinder.java文件中的:
* (1)Stub :基于Binder类,实现了AIDL接口,主要同服务端来使用,定义为一个abstract类,因为具体的服务
* 函数由程序员实现,所以aidl文件中定义的接口在stub类中并没有实现,同时Stub类中重载了onTransact
* 方法,由于transact()方法内部给包裹写入参数的顺序是由aidl工具定义的,所以在onTransact()方法中,
* AIDL工具自然知道按什么顺序从包裹中取出数据。
* (2)Proxy类:客户端访问服务端的代理,所谓的代理主要 就是为了前面所提到的第二个重要的问题:统一包裹
* 内写入数据的顺序。
*
* 这是是Server端,要实现真正的功能。
*/
private IFruitBinder.Stub serviceBinder = new IFruitBinder.Stub() {
@Override
public String getInfo() throws RemoteException {
return "I am a server";
}
@Override
public Fruit getFruit() throws RemoteException {
return mFruit;
}
} ;
}
定义一个测试类Activity
/**
* Created by lsj on 2015/9/16.
*/
public class FruitAidlActivity extends BaseActivity {
private Button btn ;
private IFruitBinder iFruitBinder;
private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iFruitBinder = IFruitBinder.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iFruitBinder = null;
}
} ;
@Override
protected void findView() {
setContentView(R.layout.activity_aidl);
}
@Override
protected void initView() {
btn = (Button)findViewById(R.id.aidl_button);
// Intent serviceIntent = new Intent(this, FruitAidlService.class);
//bindService(serviceIntent, sc,BIND_AUTO_CREATE);
}
@Override
protected void setOnClickListener() {
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.aidl_button:
if (iFruitBinder == null){
Log.d(TAG, "ibinder == null");
}else {
try{
String str = "getFruit:"+ iFruitBinder.getFruit().getName()+
iFruitBinder.getFruit().getColor()+ iFruitBinder.getFruit().getNumber();
Log.d(TAG, str);
}catch (RemoteException e ){
e.printStackTrace();
}
}
break;
default:
break;
}
}
@Override
protected void onStart() {
super.onStart();
Intent serviceIntent = new Intent(this, FruitAidlService.class);
bindService(serviceIntent, sc, BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(sc);
}
}
备注
在aidl中使用自定义的类后,要修改app下的build.gradle文件,在android添加如下
//加了后消除aidl中自定义类时找不到的问题
//参考 http://blog.csdn.net/wuyuxing24/article/details/46948961
//http://stackoverflow.com/questions/16596352/android-studio-cant-find-an-aidl-interface-for-use-in-class
//里面有google的文档
sourceSets {
main {
//Manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/aidl']
resources.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
为什么android选用Binder来实现进程间通信?
一、可靠性。在移动设备上,通常采用基于Client-Server的通信方式来实现互联网与设备间的内部通信。目前linux支持IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。Android系统为开发者提供了丰富进程间通信的功能接口,媒体播放,传感器,无线传输。这些功能都由不同的server来管理。开发都只关心将自己应用程序的client与server的通信建立起来便可以使用这个服务。毫无疑问,如若在底层架设一套协议来实现Client-Server通信,增加了系统的复杂性。在资源有限的手机 上来实现这种复杂的环境,可靠性难以保证。
二、传输性能。socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。虽然共享内存无需拷贝,但控制复杂。比较各种IPC方式的数据拷贝次数。共享内存:0次。Binder:1次。Socket/管道/消息队列:2次。
三、安全性。Android是一个开放式的平台,所以确保应用程序安全是很重要的。Android对每一个安装应用都分配了UID/PID,其中进程的UID是可用来鉴别进程身份。传统的只能由用户在数据包里填写UID/PID,这样不可靠,容易被恶意程序利用。而我们要求由内核来添加可靠的UID。
所以,出于可靠性、传输性、安全性。android建立了一套新的进程间通信方式。
相关参考
binder机制的简要理解
http://www.linuxidc.com/Linux/2012-07/66195.htm
binder机制的设计分析(1)
http://www.linuxidc.com/Linux/2015-01/111148.htm
binder机制的设计分析(2)
http://www.linuxidc.com/Linux/2012-07/66196p2.htm
binder的讲解(综合)
http://www.cnblogs.com/angeldevil/p/3296381.html
aidl中出现自定义类时
http://www.cnblogs.com/xilinch/archive/2012/07/16/2593236.html
所有评论(0)