Android进程间Binder通信机制
本文首先概述了Android的进程间通信的Binder机制,然后结合一个AIDL的例子,对Binder机制进行了解析。
概述
我们知道,在Android app中的众多activity,service等组件可以运行在同一进程中,也可以运行在不同进程中。当组件运行在同一进程中进行通信就显得比较简单,在之前的Android线程间通信机制中已经讲过了;而当它们运行在不同的进程中时,就需要使用我们本文中所要介绍的Binder机制了。
Binder作为一种进程间通信机制,负责提供远程调用的功能(RPC),它的系统组件主要包括四种:Client, Server, ServiceManager, Binder Driver. 它们之间的关系如下图所示:
从图中我们可以看出,Client, Server, ServiceManager运行在系统的用户态,而Binder Driver运行在内核态。为了完成Client端到Server端的通信任务,用户空间的需要操作Binder Driver提供的/dev/binder文件来完成交互。那么ServiceManager的工作是什么呢?ServiceManager负责管理Server并向Client端提供一个Server的代理接口(proxy)。通过代理接口中定义的方法,Client端就可以使用Server端提供的服务了。整个过程如下:
- Client端调用代理接口的方法,将Client的参数打包为parcel对象发送给内核空间中BinderDriver;
- Server端读取到BinderDriver中的请求数据,将parcel对象解包并处理;
- 处理好后,将处理结果打包返回给BinderDriver,再交给Client端。
另外,Client端与Server端的调用过程是同步的,即在Server返回结果之前,Client端是阻塞的。调用过程如下所示:
OK,下面我们通过AIDL的一个例子来分析一下以上过程时如何进行的。
AIDL小例子
首先,创建一个aidl文件“ICalculateAIDL.aidl”,这个接口里面定义了要对外提供的服务,我们这里定义了计算加法和减法的函数:
1 package com.cqumonk.calculate.aidl; 2 3 interface ICalculateAIDL { 4 int add(int a,int b); 5 int minus(int a,int b); 6 }
创建好后,rebuild一下我们的项目,可以生成同名的java文件。这是一个接口,里面包含了一个名为Stub的静态抽象类,以及我们在aidl文件中定义的加减法函数。里面详细代码在后面分析,接口文件结构如下:
然后我们需要实现服务端方面的功能,创建一个service,我们顺手把生命周期中各方法都打印出来,并完成加法和减法函数的实现:
1 public class CalculateService extends Service { 2 private static final String TAG="SERVER"; 3 public CalculateService() { 4 } 5 6 @Override 7 public void onCreate() { 8 super.onCreate(); 9 Log.e(TAG,"OnCreate"); 10 } 11 12 @Override 13 public int onStartCommand(Intent intent, int flags, int startId) { 14 Log.e(TAG,"onStartCommand"); 15 return super.onStartCommand(intent, flags, startId); 16 } 17 18 @Override 19 public IBinder onBind(Intent intent) { 20 Log.e(TAG,"onBind"); 21 return mBinder; 22 } 23 24 25 @Override 26 public void onDestroy() { 27 super.onDestroy(); 28 Log.e(TAG,"onDestroy"); 29 } 30 31 @Override 32 public boolean onUnbind(Intent intent) { 33 Log.e(TAG,"onUnbind"); 34 return super.onUnbind(intent); 35 } 36 37 @Override 38 public void onRebind(Intent intent) { 39 Log.e(TAG,"onRebind"); 40 super.onRebind(intent); 41 } 42 43 private final ICalculateAIDL.Stub mBinder=new ICalculateAIDL.Stub(){ 44 @Override 45 public int add(int a, int b) throws RemoteException { 46 return a+b; 47 } 48 49 @Override 50 public int minus(int a, int b) throws RemoteException { 51 return a-b; 52 } 53 }; 54 }
在service中,我们使用刚刚生成的ICalculateAIDL.Stub静态抽象类创建了一个Binder对象,实现了其中有关计算的业务方法,并在onBind方法中返回它。另外我们还需要在manifest文件中对该服务进行注册:
1 <service 2 android:name=".CalculateService" 3 android:enabled="true" 4 android:exported="true" > 5 <intent-filter> 6 <action android:name="com.cqumonk.adil.calculate"/> 7 <category android:name="android.intent.category.DEFAULT"/> 8 </intent-filter> 9 </service>
这时候,我们的服务端工作就完成了。我们开始编写客户端的代码来调用这个服务。首先,我们定义一个activity,包含四个按钮:
然后我们可以在activity中启动之前定义好的service并调用它所提供的服务:
1 public class MainActivity extends Activity implements View.OnClickListener { 2 3 Button mBind; 4 Button mUnbind; 5 Button mAdd; 6 Button mMinus; 7 TextView txt_res; 8 private static final String TAG="CLIENT"; 9 private ICalculateAIDL mCalculateAIDL; 10 private boolean binded=false; 11 @Override 12 protected void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 setContentView(R.layout.activity_main); 15 mBind= (Button) findViewById(R.id.btn_bind); 16 mUnbind= (Button) findViewById(R.id.btn_unbind); 17 mAdd= (Button) findViewById(R.id.btn_add); 18 mMinus= (Button) findViewById(R.id.btn_minus); 19 txt_res= (TextView) findViewById(R.id.txt_res); 20 21 mBind.setOnClickListener(this); 22 mUnbind.setOnClickListener(this); 23 mAdd.setOnClickListener(this); 24 mMinus.setOnClickListener(this); 25 } 26 27 @Override 28 protected void onStop() { 29 30 super.onStop(); 31 unbind(); 32 } 33 34 private void unbind(){ 35 if (binded){ 36 unbindService(mConnection); 37 binded=false; 38 } 39 } 40 41 @Override 42 public void onClick(View v) { 43 int id=v.getId(); 44 switch (id){ 45 case R.id.btn_bind: 46 Intent intent=new Intent(); 47 intent.setAction("com.cqumonk.adil.calculate"); 48 bindService(intent,mConnection, Context.BIND_AUTO_CREATE); 49 break; 50 case R.id.btn_unbind: 51 unbind(); 52 break; 53 case R.id.btn_add: 54 if(mCalculateAIDL!=null){ 55 try { 56 int res=mCalculateAIDL.add(3,3); 57 txt_res.setText(res+""); 58 } catch (RemoteException e) { 59 e.printStackTrace(); 60 } 61 }else{ 62 Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show(); 63 } 64 break; 65 case R.id.btn_minus: 66 if(mCalculateAIDL!=null){ 67 try { 68 int res=mCalculateAIDL.minus(9,4); 69 txt_res.setText(res+""); 70 } catch (RemoteException e) { 71 e.printStackTrace(); 72 } 73 }else{ 74 Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show(); 75 } 76 break; 77 } 78 } 79 private ServiceConnection mConnection=new ServiceConnection() { 80 @Override 81 public void onServiceConnected(ComponentName name, IBinder service) { 82 Log.e(TAG,"connect"); 83 binded=true; 84 mCalculateAIDL=ICalculateAIDL.Stub.asInterface(service); 85 86 } 87 88 @Override 89 public void onServiceDisconnected(ComponentName name) { 90 Log.e(TAG,"disconnect"); 91 mCalculateAIDL=null; 92 binded=false; 93 } 94 }; 95 }
当我们点击绑定按钮,观察日志:
点击加法和减法按钮,都可以完成计算并返回值。然后点击解绑按钮:
我们并未发现有连接断开的日志打印,这时候,我们继续点击加减法按钮,发现仍然可以完成计算功能,这又是为毛呢?我们看到unbind和destroy的日志打印,说明连接已经断开,service已经被销毁,但是我们返回的stub对象仍然是可以继续使用的。而并不是说service仍然在运行。
过程分析
首先,我们调用bindservice方法来启动了service:
一方面连接成功时调用了serviceConnection的onServiceConnected方法。我们从该方法中获取到一个Binder对象(注意,这个binder并不是我们在service中实现的那个哦。当service时本apk中的service时,这里返回的是同一个binder),我们通过此binder来与server进行通信。为了区分,我们称为clientBinder。
1 public void onServiceConnected(ComponentName name, IBinder service) { 2 Log.e(TAG,"connect"); 3 binded=true; 4 mCalculateAIDL= ICalculateAIDL.Stub.asInterface(service); 5 }
另一方面,onBind方法返回了我们实现的Stub对象,其实也是一个Binder,用于和Client进行通信。(之前我们定义了一个aidl接口文件,并根据它生成了ICalculateAIDL接口。这个接口中有我们定义的两个方法以及一个静态抽象内部类Stub,它是一个Binder的子类。)我们来看一下Stub是如何定义的:
1 public static abstract class Stub extends android.os.Binder implements com.cqumonk.calculate.aidl.ICalculateAIDL
它继承了Binder类也实现了ICalculateAIDL接口,我们实现了定义的add和minus方法。我们仍然要强调一下它并不是clientBinder,在负责与clientBinder进行通信交互的同时,它也维护了service描述符与服务端service的映射。
我们在Client中的onServiceConnected里调用了stub对象的asInterface方法,并将之前得到的clientBinder传入:
1 public static com.cqumonk.calculate.aidl.ICalculateAIDL asInterface(android.os.IBinder obj) { 2 if ((obj == null)) { 3 return null; 4 } 5 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//根据包名获取本地实现的一个接口的实例,如果是本地service则可以获取到 6 if (((iin != null) && (iin instanceof com.cqumonk.calculate.aidl.ICalculateAIDL))) { 7 return ((com.cqumonk.calculate.aidl.ICalculateAIDL) iin); //如果得到的实例是ICalculateAIDL的对象,则返回 8 } 9 return new com.cqumonk.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj);//如果无法得到本地实现的对象则会返回一个代理对象 10 }
在这个方法中,首先在系统中查找注册的的service,如果没有找到,那么一定是别的apk实现的service,于是返回一个此service的静态代理类对象供Client调用。我们来看一下这个代理,创建时我们将clientBinder传入了,同时也它实现了ICalculateAIDL接口:
1 private static class Proxy implements com.cqumonk.calculate.aidl.ICalculateAIDL { 2 private android.os.IBinder mRemote; 3 4 Proxy(android.os.IBinder remote) { 5 mRemote = remote; 6 } 7 8 @Override 9 public android.os.IBinder asBinder() { 10 return mRemote; 11 } 12 13 public java.lang.String getInterfaceDescriptor() { 14 return DESCRIPTOR; 15 } 16 17 @Override 18 public int add(int a, int b) throws android.os.RemoteException { 19 android.os.Parcel _data = android.os.Parcel.obtain(); 20 android.os.Parcel _reply = android.os.Parcel.obtain(); 21 int _result; 22 try { 23 _data.writeInterfaceToken(DESCRIPTOR); 24 _data.writeInt(a); //将参数打包 25 _data.writeInt(b); 26 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); //调用binderDriver的提供的方法将参数发给服务端 27 _reply.readException(); 28 _result = _reply.readInt(); //读取到返回结果 29 } finally { 30 _reply.recycle(); 31 _data.recycle(); 32 } 33 return _result; 34 } 35 36 @Override 37 public int minus(int a, int b) throws android.os.RemoteException { 38 android.os.Parcel _data = android.os.Parcel.obtain(); 39 android.os.Parcel _reply = android.os.Parcel.obtain(); 40 int _result; 41 try { 42 _data.writeInterfaceToken(DESCRIPTOR); 43 _data.writeInt(a); 44 _data.writeInt(b); 45 mRemote.transact(Stub.TRANSACTION_minus, _data, _reply, 0); 46 _reply.readException(); 47 _result = _reply.readInt(); 48 } finally { 49 _reply.recycle(); 50 _data.recycle(); 51 } 52 return _result; 53 } 54 }
代理中也实现了ICalculateAIDL接口定义的方法,我们以add方法为例,里面将参数打包发送给Server端。在Server端收到请求后,会调用service中我们实现的那个stub对象(mBinder)的onTransact方法:
1 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { 2 switch (code) { 3 case INTERFACE_TRANSACTION: { 4 reply.writeString(DESCRIPTOR); 5 return true; 6 } 7 case TRANSACTION_add: { 8 data.enforceInterface(DESCRIPTOR); 9 int _arg0; 10 _arg0 = data.readInt(); 11 int _arg1; 12 _arg1 = data.readInt(); 13 int _result = this.add(_arg0, _arg1); //调用我们实现好的方法 14 reply.writeNoException(); 15 reply.writeInt(_result); //把结果返回 16 return true; 17 } 18 case TRANSACTION_minus: { 19 data.enforceInterface(DESCRIPTOR); 20 int _arg0; 21 _arg0 = data.readInt(); 22 int _arg1; 23 _arg1 = data.readInt(); 24 int _result = this.minus(_arg0, _arg1); 25 reply.writeNoException(); 26 reply.writeInt(_result); 27 return true; 28 } 29 } 30 return super.onTransact(code, data, reply, flags); 31 }
调用完成后把结果打包返回给Poxy处理,最后返回给客户端。
总结
由上面的例子我们可以看出,在跨进程通信的时候,Client端使用的Poxy里面封装了一个binder与Server端的stub(也是一个binder对象)进行交互,两个binder作为接口调用BinderDriver的transact来发送数据包,以及onTransact接收处理数据包。