http://blog.sina.com.cn/s/blog_3f7f41d40100dpum.html
遠程過程調用
Android擁有輕量級的遠程調用機制 (RPC) — 方法在本地調用,在遠程執行(在其它進程中),結果返回給調用者。 這意味着將方法調用及其附帶的數據分解爲操作系統可以理解的形式,將其由本地進程和地址空間傳送到遠程進程和地址空間中,在遠程重新裝配並執行該調用。返 回值沿着相反的方向傳遞。Android提供了實現該機制的所有代碼,因此你只需要關注於如何定義和實現該RPC接口本身。
RPC接口只能包含方法。所有的方法都是同步執行的(本地方法被阻斷,直到遠程方法結束),即使沒有返回值。
簡而言之,該機制的流程如下:你由使用簡單的IDL(接口定義語言)定義要實現的RPC接口。根據接口的定義,
aidl
工具生成本地和遠程進程必要的Java接口的定義。它包含下圖所示的兩個內部類:
內部類中包含管理你用IDL生成的遠程過程調用需要的所有代碼。兩個內部類都實現了IBinder 接口。其中一個在本地由系統內部使用,寫代碼時可以忽略它。另一個叫做 Stub,擴展自Binder 類。作爲對執行IPC調用的內部代碼補充,它包含你在RPC接口中聲明的方法。象圖中說明的那樣, 你應該繼承Stub來實現這些方法。
一般遠程過程由服務來管理(因爲服務可以通知系統關於進程和它連接的其它進程的信息)。它既有aidl
。服務的客戶端只有由aidl
生成的接口文件。
接下來是服務和其客戶端是如何建立連接的:
- 服務的客戶端(爲位於本地)應該實現
onServiceConnected()
和onServiceDisconnected()
方法,這樣它們就可以在成功與遠程服務建立或斷開連接後收到消息。它們應該調用bindService()
來設置連接。 - 服務的
onBind()
方法應該被實現用作根據收到的意圖(傳入bindService()
的意圖),決定接受或拒絕連接。 - 如果連接被接受,它返回一個Stub的子類。如果服務接受了連接,Android調用客戶端的
onServiceConnected()
方法並傳入一個IBinder對象,由服務管理的Stub子類的代理。通過該代理,客戶端可以調用遠程服務。
上述簡單的描述忽略了一些RPC機制的細節。更多信息參見用AIDL設計遠程接口 和IBinder 類的描述。
在Android中, 每個應用程序都可以有自己的進程. 在寫UI應用的時候, 經常要用到Service. 在不同的進程中, 怎樣傳遞對象呢? 顯然, Java中不允許跨進程內存共享. 因此傳遞對象, 只能把對象拆分成操作系統能理解的簡單形式, 以達到跨界對象訪問的目的. 在J2EE中,採用RMI的方式, 可以通過序列化傳遞對象. 在Android中, 則採用AIDL的方式. 理論上AIDL可以傳遞Bundle,實際上做起來卻比較麻煩.
AIDL(AndRoid接口描述語言)是一種藉口描述語言; 編譯器可以通過aidl文件生成一段代碼,通過預先定義的接口達到兩個進程內部通信進程的目的. 如果需要在一個Activity中, 訪問另一個Service中的某個對象, 需要先將對象轉化成AIDL可識別的參數(可能是多個參數), 然後使用AIDL來傳遞這些參數, 在消息的接收端, 使用這些參數組裝成自己需要的對象.
AIDL的IPC的機制和COM或CORBA類似, 是基於接口的,但它是輕量級的。它使用代理類在客戶端和實現層間傳遞值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相關類.; 2. 調用aidl產生的class.
具體實現步驟如下:
1、創建AIDL文件, 在這個文件裏面定義接口, 該接口定義了可供客戶端訪問的方法和屬性。 如: ITaskBinder.adil
package com.cmcc.demo;
import com.cmcc.demo.ITaskCallback;
interface ITaskBinder {
boolean isTaskRunning();
void stopRunningTask();
void registerCallback(ITaskCallback cb);
void unregisterCallback(ITaskCallback cb);
}
其中: ITaskCallback在文件ITaskCallback.aidl中定義:
package com.cmcc.demo;
interface ITaskCallback {
void actionPerformed(int actionId);
}
注意: 理論上, 參數可以傳遞基本數據類型和String, 還有就是Bundle的派生類, 不過在Eclipse中,目前的ADT不支持Bundle做爲參數, 據說用Ant編譯可以, 我沒做嘗試.
2、編譯AIDL文件, 用Ant的話, 可能需要手動, 使用Eclipse plugin的話,可以根據adil文件自動生產java文件並編譯, 不需要人爲介入.
3、在Java文件中, 實現AIDL中定義的接口. 編譯器會根據AIDL接口, 產生一個JAVA接口。這個接口有一個名爲Stub的內部抽象類,它繼承擴展了接口並實現了遠程調用需要的幾個方法。接下來就需要自己去實現自定義的幾個接口了.
ITaskBinder.aidl中接口的實現, 在MyService.java中接口以內嵌類的方式實現:
private final ITaskBinder.Stub mBinder = new ITaskBinder.Stub() {
public void stopRunningTask() {
}
public boolean isTaskRunning() {
return false;
}
public void registerCallback(ITaskCallback cb) {
if (cb != null) mCallbacks.register(cb);
}
public void unregisterCallback(ITaskCallback cb) {
if (cb != null) mCallbacks.unregister(cb);
}
};
在MyActivity.java中ITaskCallback.aidl接口實現:
private ITaskCallback mCallback = new ITaskCallback.Stub() {
public void actionPerformed(int id) {
//TODO
printf("callback id=" + id);
}
};
4、向客戶端提供接口ITaskBinder, 如果寫的是service,擴展該Service並重載onBind ()方法來返回一個實現上述接口的類的實例。這個地方返回的mBinder,就是上面通過內嵌了定義的那個. (MyService.java)
public IBinder onBind(Intent t) {
printf("service on bind");
return mBinder;
}
在Activity中, 可以通過Binder定義的接口, 來進行遠程調用.
5、在服務器端回調客戶端的函數. 前提是當客戶端獲取的IBinder接口的時候,要去註冊回調函數, 只有這樣, 服務器端才知道該調用那些函數在:MyService.java中:
void callback(int val) {
final int N = mCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
try {
mCallbacks.getBroadcastItem(i).actionPerformed(val);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mCallbacks.finishBroadcast();
}
AIDL的創建方法:
AIDL語法很簡單,可以用來聲明一個帶一個或多個方法的接口,也可以傳遞參數和返回值。 由於遠程調用的需要, 這些參數和返回值並不是任何類型.下面是些AIDL支持的數據類型:
1. 不需要import聲明的簡單Java編程語言類型(int,boolean等)
2. String, CharSequence不需要特殊聲明
3. List, Map和Parcelables類型, 這些類型內所包含的數據成員也只能是簡單數據類型,
String等其他比支持的類型.
(
(另外: 我沒嘗試Parcelables, 在Eclipse+ADT下編譯不過, 或許以後會有所支持).
下面是AIDL語法:
// 文件名: SomeClass.aidl
// 文件可以有註釋, 跟java的一樣
// 在package以前的註釋, 將會被忽略.
// 函數和變量以前的註釋, 都會被加入到生產java代碼中.
package com.cmcc.demo;
// import 引入語句
import com.cmcc.demo.ITaskCallback;
interface ITaskBinder {
//函數跟java一樣, 可以有0到多個參數 ,可以有一個返回值
boolean isTaskRunning();
void stopRunningTask();
//參數可以是另外的一個aidl定義的接口
void registerCallback(ITaskCallback cb);
void unregisterCallback(ITaskCallback cb);
//參數可以是String, 可以用in表入輸入類型, out表示輸出類型.
int getCustomerList(in String branch, out String[]
customerList);
}
實現接口時有幾個原則:
.拋出的異常不要返回給調用者. 跨進程拋異常處理是不可取的.
.IPC調用是同步的。如果你知道一個IPC服務需要超過幾毫秒的時間才能完成地話,你應該避免在Activity的主線程中調用。也就是IPC調用會掛起應用程序導致界面失去響應.
這種情況應該考慮單起一個線程來處理.
.不能在AIDL接口中聲明靜態屬性。
IPC的調用步驟:
1. 聲明一個接口類型的變量,該接口類型在.aidl文件中定義。
2. 實現ServiceConnection。
3.
調用ApplicationContext.bindService(),並在ServiceConnection實現中進行傳遞.
4.
在ServiceConnection.onServiceConnected()實現中,你會接收一個IBinder實例(被調用的Service).
調用
YourInterfaceName.Stub.asInterface((IBinder)service)將參數轉換爲YourInterface類型。
5.
調用接口中定義的方法。你總要檢測到DeadObjectException異常,該異常在連接斷開時被拋出。它只會被遠程方法拋出。
6. 斷開連接,調用接口實例中的ApplicationContext.unbindService()
下面是整個程序:
1. ITaskCallback.aidl
package com.cmcc.demo;
interface ITaskCallback {
void actionPerformed(int actionId);
}
2. ITaskBinder.aidl
package com.cmcc.demo;
import com.cmcc.demo.ITaskCallback;
interface ITaskBinder {
boolean isTaskRunning();
void stopRunningTask();
void registerCallback(ITaskCallback cb);
void unregisterCallback(ITaskCallback cb);
}
3. MyService.java
package com.cmcc.demo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
public class MyService extends Service {
@Override
public void onCreate() {
printf("service create");
}
@Override
public void onStart(Intent intent, int startId) {
printf("service start id=" + startId);
callback(startId);
}
@Override
public IBinder onBind(Intent t) {
printf("service on bind");
return mBinder;
}
@Override
public void onDestroy() {
printf("service on destroy");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
printf("service on unbind");
return super.onUnbind(intent);
}
public void onRebind(Intent intent) {
printf("service on rebind");
super.onRebind(intent);
}
private void printf(String str) {
Log.e("TAG", "###################------ " + str + "------");
}
void callback(int val) {
final int N = mCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
try {
mCallbacks.getBroadcastItem(i).actionPerformed(val);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mCallbacks.finishBroadcast();
}
private final ITaskBinder.Stub mBinder = new ITaskBinder.Stub() {
public void stopRunningTask() {
}
public boolean isTaskRunning() {
return false;
}
public void registerCallback(ITaskCallback cb) {
if (cb != null) mCallbacks.register(cb);
}
public void unregisterCallback(ITaskCallback cb) {
if (cb != null) mCallbacks.unregister(cb);
}
};
final RemoteCallbackList<ITaskCallback> mCallbacks
= new RemoteCallbackList<ITaskCallback>();
}
4. MyActivity.java
package com.cmcc.demo;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.AbsoluteLayout;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.PrintWriter;
public class MyActivity extends Activity {
private Button btnOk;
private Button btnCancel;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.test_service);
btnOk = (Button)findViewById(R.id.btn_ok);
btnCancel = (Button)findViewById(R.id.btn_cancel);
btnOk.setText("Start Service");
btnCancel.setTag("Stop Service");
btnOk.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onOkClick();
}
});
btnCancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onCancelClick();
}
});
}
void onOkClick() {
Bundle args = new Bundle();
Intent intent = new Intent(this, MyService.class);
intent.putExtras(args);
//printf("send intent to start");
//startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
startService(intent);
}
void onCancelClick() {
Intent intent = new Intent(this, MyService.class);
//printf("send intent to stop");
unbindService(mConnection);
//stopService(intent);
}
private void printf(String str) {
Log.e("TAG", "###################------ " + str + "------");
}
ITaskBinder mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mService = ITaskBinder.Stub.asInterface(service);
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
private ITaskCallback mCallback = new ITaskCallback.Stub() {
public void actionPerformed(int id) {
printf("callback id=" + id);
}
};
}