AIDL的作用
在Android平臺,每個應用程序都是一個單獨的JVM,都運行在自己的進程空間裏, 通常,一個進程不允許訪問另一個進程的內存空間(一個應用不能訪問另一個應用)。當用戶(程序開發人員)想在一個App中訪問另一個App的進程空間的時候,就需要進程間通信。在Android中,遠程服務爲我們提供了實現進程間通信的方式,其中,AIDL是應用程序開發人員常的一種方式。
AIDL (Android Interface Definition Language) 是一種IDL 語言,用於生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。換句比較淺顯的話來說,就是我這個App應用的activity,需要調用其他App應用的Service.當然同一App應用的activity 與service也可以在不同進程間,這可以設置Service配置中,android:process=":remote"。
AIDL IPC機制是面向接口的,像COM或Corba一樣,但是更加輕量級。它是使用代理類在客戶端和實現端傳遞數據。
可以看出,aidl的適用場景爲: 只有你允許客戶端從不同的應用程序爲了進程間的通信而去訪問你的service時,你可以使用AIDl來實現。例如,百度地圖給我們提供了下面的service:com.baidu.location.f,我們只要在我們的應用程序的manifest.xml文件中聲明這個service,就能用它提供的服務了。:
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>
定義AIDL接口
AIDL接口文件,和普通的接口內容沒有什麼特別,只是它的擴展名爲.aidl。保存在src目錄下。如果其他應用程序需要IPC,則那些應用程序的src也要帶有這個文件。Android SDK tools就會在gen目錄自動生成一個IBinder接口文件。service必須適當地實現這個IBinder接口。那麼客戶端程序就能綁定這個service並在IPC(Inter-Process Communication,進程間通信)時從IBinder調用方法。
每個aidl文件只能定義一個接口,而且只能是接口的聲明和方法的聲明。
1.創建.aidl文件
AIDL只支持方法,不能定義靜態成員,並且方法也不能有類似public等的修飾符;AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。
其中對於Java編程語言的基本數據類型 (int, long, char, boolean等),String和CharSequence,集合接口類型List和Map,不需要import 語句。
而如果需要在AIDL中使用其他AIDL接口類型,需要import,即使是在相同包結構下。AIDL允許傳遞實現Parcelable接口的類,需要import.
需要特別注意的是,對於非基本數據類型,也不是String和CharSequence類型的,需要有方向指示,包括in、out和inout,in表示由客戶端設置,out表示由服務端設置,inout是兩者均可設置。
AIDL只支持接口方法,不能公開static變量。
例如 (IMyService.aidl):
package com.demo;
import com.demo.Person;
interface IMyService {
void savePersonInfo(in Person person);
List<Person> getAllPerson();
}
2.實現接口
創建一個類實現剛纔那個aidl的接口:
public class RemoteService extends Service {
private LinkedList<Person> personList = new LinkedList<Person>();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IMyService.Stub mBinder = new IMyService.Stub(){
@Override
public void savePersonInfo(Person person) throws RemoteException {
if (person != null){
personList.add(person);
}
}
@Override
public List<Person> getAllPerson() throws RemoteException {
return personList;
}
};
}
這裏會看到有一個名爲IMyService.Stub類,查看aidl文件生成的Java文件源代碼就能發現有這麼一段代碼:
public static abstract class Stub extends android.os.Binder implements com.demo.IMyService
原來Stub類就是繼承於Binder類,也就是說RemoteService類和普通的Service類沒什麼不同,只是所返回的IBinder對象比較特別,是一個實現了AIDL接口的Binder。
接下來就是關於所傳遞的數據Bean——Person類,是一個序列化的類,這裏使用Parcelable 接口來序列化,是Android提供的一個比Serializable 效率更高的序列化類。
Parcelable需要實現三個函數:
1) void writeToParcel(Parcel dest, int flags) 將需要序列化存儲的數據寫入外部提供的Parcel對象dest。而看了網上的代碼例子,個人猜測,讀取Parcel數據的次序要和這裏的write次序一致,否則可能會讀錯數據。具體情況我沒試驗過!
2) describeContents() 沒搞懂有什麼用,反正直接返回0也可以
3) static final Parcelable.Creator對象CREATOR 這個CREATOR命名是固定的,而它對應的接口有兩個方法:
createFromParcel(Parcel source) 實現從source創建出JavaBean實例的功能
newArray(int size) 創建一個類型爲T,長度爲size的數組,僅一句話(return new T[size])即可。估計本方法是供外部類反序列化本類數組使用。
仔細觀察Person類的代碼和上面所說的內容:
public class Person implements Parcelable {
private String name;
private String telNumber;
private int age;
public Person() {}
public Person(Parcel pl){
name = pl.readString();
telNumber = pl.readString();
age = pl.readInt();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelNumber() {
return telNumber;
}
public void setTelNumber(String telNumber) {
this.telNumber = telNumber;
}
public int getAge() {
return age;
}
public void setAge(int age) {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(telNumber);
dest.writeInt(age);
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
然後創建Person.aidl文件,注意這裏的parcelable和原來實現的Parcelable 接口,開頭的字母p一個小寫一個大寫:
package com.demo;
parcelable Person;
對於實現AIDL接口,官方還提醒我們:
1. 調用者是不能保證在主線程執行的,所以從一調用的開始就需要考慮多線程處理,以及確保線程安全;
2. IPC調用是同步的。如果你知道一個IPC服務需要超過幾毫秒的時間才能完成地話,你應該避免在Activity的主線程中調用。也就是IPC調用會掛起應用程序導致界面失去響應,這種情況應該考慮單獨開啓一個線程來處理。
3. 拋出的異常是不能返回給調用者(跨進程拋異常處理是不可取的)。
3. 客戶端獲取接口
客戶端如何獲取AIDL接口呢?通過IMyService.Stub.asInterface(service)來得到IMyService對象:
private IMyService mRemoteService;
private ServiceConnection mRemoteConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mRemoteService = IMyService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
mRemoteService = null;
}
};
在生成的IMyService.java裏面會找到這樣的代碼:
public static com.demo.IMyService asInterface(android.os.IBinder obj) {...}
而service的綁定沒有什麼不同:
if (mIsRemoteBound) {
unbindService(mRemoteConnection);
}else{
bindService(new Intent("com.demo.IMyService"),mRemoteConnection, Context.BIND_AUTO_CREATE);
}
mIsRemoteBound = !mIsRemoteBound;
通過IPC調用/傳遞數據
客戶端綁定service後就能通過IPC來調用/傳遞數據了,直接調用service對象的接口方法:
addPersonButton.setOnClickListener(
new View.OnClickListener(){
private int index = 0;
@Override
public void onClick(View view) {
Person person = new Person();
index = index + 1;
person.setName("Person" + index);
person.setAge(20);
person.setTelNumber("123456");
try {
mRemoteService.savePersonInfo(person);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
listPersonButton.setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View view) {
List<Person> list = null;
try {
list = mRemoteService.getAllPerson();
} catch (RemoteException e) {
e.printStackTrace();
}
if (list != null){
StringBuilder text = new StringBuilder();
for(Person person : list){
text.append("\nPerson name:");
text.append(person.getName());
text.append("\n age :");
text.append(person.getAge());
text.append("\n tel number:");
text.append(person.getTelNumber());
}
inputPersonEdit.setText(text);
}else {
Toast.makeText(ServiceActivity.this, "get data error",
Toast.LENGTH_SHORT).show();
}
}
});
Permission權限
如果Service在AndroidManifest.xml中聲明瞭全局的強制的訪問權限,其他引用必須聲明權限才能來start,stop或bind這個service.
另外,service可以通過權限來保護她的IPC方法調用,通過調用checkCallingPermission(String)方法來確保可以執行這個操作。
AndroidManifest.xml的Service元素
<service android:name=".RemoteService" android:process=":remote">
<intent-filter>
<action android:name="com.demo.IMyService" />
</intent-filter>
</service>
這裏的android:process=":remote",一開始我沒有添加的,在同一個程序裏使用IPC,即同一個程序作爲客戶端/服務器端,結果運行mRemoteService = IMyService.Stub.asInterface(service);時提示空指針異常。觀察了人家的在不同程序裏進行IPC的代碼,也是沒有這個android:process=":remote"的。
也就是說android:process=":remote",代表在應用程序裏,當需要該service時,會自動創建新的進程。而如果是android:process="remote",沒有“:”分號的,則創建全局進程,不同的應用程序共享該進程。
假設A應用需要與B應用進行通信,調用B應用中的download(String path)方法,B應用以Service方式向A應用提供服務。需要下面四個步驟:
1> 在B應用中創建*.aidl文件,aidl文件的定義和接口的定義很相類,如:在cn.itcast.aidl包下創建IDownloadService.aidl文件,內容如下:
package cn.itcast.aidl;
interface IDownloadService {
void download(String path);
}
當完成aidl文件創建後,eclipse會自動在項目的gen目錄中同步生成IDownloadService.java接口文件。接口文件中生成一個Stub的抽象類,裏面包括aidl定義的方法,還包括一些其它輔助方法。值得關注的是asInterface(IBinder iBinder),它返回接口類型的實例,對於遠程服務調用,遠程服務返回給客戶端的對象爲代理對象,客戶端在onServiceConnected(ComponentName name, IBinder service)方法引用該對象時不能直接強轉成接口類型的實例,而應該使用asInterface(IBinder iBinder)進行類型轉換。
編寫Aidl文件時,需要注意下面幾點:
1.接口名和aidl文件名相同。
2.接口和方法前不用加訪問權限修飾符public,private,protected等,也不能用final,static。
3.Aidl默認支持的類型包話java基本類型(int、long、boolean等)和(String、List、Map、CharSequence),使用這些類型時不需要import聲明。對於List和Map中的元素類型必須是Aidl支持的類型。如果使用自定義類型作爲參數或返回值,自定義類型必須實現Parcelable接口。
4.自定義類型和AIDL生成的其它接口類型在aidl描述文件中,應該顯式import,即便在該類和定義的包在同一個包中。
5.在aidl文件中所有非Java基本類型參數必須加上in、out、inout標記,以指明參數是輸入參數、輸出參數還是輸入輸出參數。
6.Java原始類型默認的標記爲in,不能爲其它標記。
2> 在B應用中實現aidl文件生成的接口(本例是IDownloadService),但並非直接實現接口,而是通過繼承接口的Stub來實現(Stub抽象類內部實現了aidl接口),並且實現接口方法的代碼。內容如下:
public class ServiceBinder extends IDownloadService.Stub {
@Override
public void download(String path) throws RemoteException {
Log.i("DownloadService", path);
}
}
3> 在B應用中創建一個Service(服務),在服務的onBind(Intent intent)方法中返回實現了aidl接口的對象(本例是ServiceBinder)。內容如下:
public class DownloadService extends Service {
private ServiceBinder serviceBinder = new ServiceBinder();
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
public class ServiceBinder extends IDownloadService.Stub {
@Override
public void download(String path) throws RemoteException {
Log.i("DownloadService", path);
}
}
}
其他應用可以通過隱式意圖訪問服務,意圖的動作可以自定義,AndroidManifest.xml配置代碼如下:
<service android:name=".DownloadService" >
<intent-filter>
<action android:name="cn.itcast.process.aidl.DownloadService" />
</intent-filter>
</service>
4> 把B應用中aidl文件所在package連同aidl文件一起拷貝到客戶端A應用,eclipse會自動在A應用的gen目錄中爲aidl文件同步生成IDownloadService.java接口文件,接下來就可以在A應用中實現與B應用通信,代碼如下:
public class ClientActivity extends Activity {
private IDownloadService downloadService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.bindService(new Intent("cn.itcast.process.aidl.DownloadService"), this.serviceConnection, BIND_AUTO_CREATE);//綁定到服務
}
@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(serviceConnection);//解除服務
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadService = IDownloadService.Stub.asInterface(service);
try {
downloadService.download("http://www.itcast.cn");
} catch (RemoteException e) {
Log.e("ClientActivity", e.toString());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
downloadService = null;
}
};
}