一、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