本文首先概述了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”,這個接口裡面定義了要對外提供的服務,我們這裡定義了計算加法和減法的函數:
package com.cqumonk.calculate.aidl; interface ICalculateAIDL { int add(int a,int b); int minus(int a,int b); }
創建好後,rebuild一下我們的項目,可以生成同名的java文件。這是一個接口,裡面包含了一個名為Stub的靜態抽象類,以及我們在aidl文件中定義的加減法函數。裡面詳細代碼在後面分析,接口文件結構如下:
然後我們需要實現服務端方面的功能,創建一個service,我們順手把生命周期中各方法都打印出來,並完成加法和減法函數的實現:
public class CalculateService extends Service {
private static final String TAG="SERVER";
public CalculateService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"OnCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return mBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG,"onUnbind");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.e(TAG,"onRebind");
super.onRebind(intent);
}
private final ICalculateAIDL.Stub mBinder=new ICalculateAIDL.Stub(){
@Override
public int add(int a, int b) throws RemoteException {
return a+b;
}
@Override
public int minus(int a, int b) throws RemoteException {
return a-b;
}
};
}
在service中,我們使用剛剛生成的ICalculateAIDL.Stub靜態抽象類創建了一個Binder對象,實現了其中有關計算的業務方法,並在onBind方法中返回它。另外我們還需要在manifest文件中對該服務進行注冊:
<service
android:name=".CalculateService"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="com.cqumonk.adil.calculate"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
這時候,我們的服務端工作就完成了。我們開始編寫客戶端的代碼來調用這個服務。首先,我們定義一個activity,包含四個按鈕:
然後我們可以在activity中啟動之前定義好的service並調用它所提供的服務:
public class MainActivity extends Activity implements View.OnClickListener {
Button mBind;
Button mUnbind;
Button mAdd;
Button mMinus;
TextView txt_res;
private static final String TAG="CLIENT";
private ICalculateAIDL mCalculateAIDL;
private boolean binded=false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBind= (Button) findViewById(R.id.btn_bind);
mUnbind= (Button) findViewById(R.id.btn_unbind);
mAdd= (Button) findViewById(R.id.btn_add);
mMinus= (Button) findViewById(R.id.btn_minus);
txt_res= (TextView) findViewById(R.id.txt_res);
mBind.setOnClickListener(this);
mUnbind.setOnClickListener(this);
mAdd.setOnClickListener(this);
mMinus.setOnClickListener(this);
}
@Override
protected void onStop() {
super.onStop();
unbind();
}
private void unbind(){
if (binded){
unbindService(mConnection);
binded=false;
}
}
@Override
public void onClick(View v) {
int id=v.getId();
switch (id){
case R.id.btn_bind:
Intent intent=new Intent();
intent.setAction("com.cqumonk.adil.calculate");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.btn_unbind:
unbind();
break;
case R.id.btn_add:
if(mCalculateAIDL!=null){
try {
int res=mCalculateAIDL.add(3,3);
txt_res.setText(res+"");
} catch (RemoteException e) {
e.printStackTrace();
}
}else{
Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_minus:
if(mCalculateAIDL!=null){
try {
int res=mCalculateAIDL.minus(9,4);
txt_res.setText(res+"");
} catch (RemoteException e) {
e.printStackTrace();
}
}else{
Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
}
break;
}
}
private ServiceConnection mConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG,"connect");
binded=true;
mCalculateAIDL=ICalculateAIDL.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG,"disconnect");
mCalculateAIDL=null;
binded=false;
}
};
}
當我們點擊綁定按鈕,觀察日志:
點擊加法和減法按鈕,都可以完成計算並返回值。然後點擊解綁按鈕:
我們並未發現有連接斷開的日志打印,這時候,我們繼續點擊加減法按鈕,發現仍然可以完成計算功能,這又是為毛呢?我們看到unbind和destroy的日志打印,說明連接已經斷開,service已經被銷毀,但是我們返回的stub對象仍然是可以繼續使用的。而並不是說service仍然在運行。
過程分析
首先,我們調用bindservice方法來啟動了service:
一方面連接成功時調用了serviceConnection的onServiceConnected方法。我們從該方法中獲取到一個Binder對象(注意,這個binder並不是我們在service中實現的那個哦。當service時本apk中的service時,這裡返回的是同一個binder),我們通過此binder來與server進行通信。為了區分,我們稱為clientBinder。
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG,"connect");
binded=true;
mCalculateAIDL= ICalculateAIDL.Stub.asInterface(service);
}
另一方面,onBind方法返回了我們實現的Stub對象,其實也是一個Binder,用於和Client進行通信。(之前我們定義了一個aidl接口文件,並根據它生成了ICalculateAIDL接口。這個接口中有我們定義的兩個方法以及一個靜態抽象內部類Stub,它是一個Binder的子類。)我們來看一下Stub是如何定義的:
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傳入:
public static com.cqumonk.calculate.aidl.ICalculateAIDL asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//根據包名獲取本地實現的一個接口的實例,如果是本地service則可以獲取到
if (((iin != null) && (iin instanceof com.cqumonk.calculate.aidl.ICalculateAIDL))) {
return ((com.cqumonk.calculate.aidl.ICalculateAIDL) iin); //如果得到的實例是ICalculateAIDL的對象,則返回
}
return new com.cqumonk.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj);//如果無法得到本地實現的對象則會返回一個代理對象
}
在這個方法中,首先在系統中查找注冊的的service,如果沒有找到,那麼一定是別的apk實現的service,於是返回一個此service的靜態代理類對象供Client調用。我們來看一下這個代理,創建時我們將clientBinder傳入了,同時也它實現了ICalculateAIDL接口:
private static class Proxy implements com.cqumonk.calculate.aidl.ICalculateAIDL {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a); //將參數打包
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); //調用binderDriver的提供的方法將參數發給服務端
_reply.readException();
_result = _reply.readInt(); //讀取到返回結果
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public int minus(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_minus, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
代理中也實現了ICalculateAIDL��口定義的方法,我們以add方法為例,裡面將參數打包發送給Server端。在Server端收到請求後,會調用service中我們實現的那個stub對象(mBinder)的onTransact方法:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1); //調用我們實現好的方法
reply.writeNoException();
reply.writeInt(_result); //把結果返回
return true;
}
case TRANSACTION_minus: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.minus(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
調用完成後把結果打包返回給Poxy處理,最後返回給客戶端。
總結
由上面的例子我們可以看出,在跨進程通信的時候,Client端使用的Poxy裡面封裝了一個binder與Server端的stub(也是一個binder對象)進行交互,兩個binder作為接口調用BinderDriver的transact來發送數據包,以及onTransact接收處理數據包。
通過結合AIDL例子,我們對Android進程間的通信機制進行了分析,如果有錯誤的地方,歡迎指正。
更多Android相關信息見Android 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=11