在上一篇《Android核心組件之Service》博文中我們詳細講解了Bound
Service的兩種方法(繼承Binder和使用Messenger),當只需在本應用程序綁定 Service而不需要執行進程間通信時,繼承Binder的方法爲較好的選擇,而若需要在不同的應用程序執行進程間的通信,但不需要在Service中使用多線程訪問時,則可以使用Messenger來完成我們的需求。不過,若即需要在不同的應用程序中執行進程間的通信,也要在Service中使用多線程訪問,那麼我們該怎麼辦呢?別急,Android已經爲我們提供瞭解決方法,就是使用AIDL來實現進程間的通信,接下來,我們將會詳細講解AIDL的原理和使用方法。
AIDL是什麼
AIDL(Android Interface Definition Language),它是一種類似於IDLs的Android接口定義語言,它可以生成Android設備上兩個進程之間進行進程間通信的代碼,然後來完成客戶端和服務器端之間的交流。在Android系統中,一般情況下一個進程是無法訪問另外一個進程的內存的,而使用代碼編組來實現是又比較的冗長乏味,那麼我們就需要把對象分解成操作系統能夠理解的基本單元,安全的通過進程邊界。那麼,我們可以使用AIDL來完成這些工作。
創建.aidl文件
AIDL IPC機制是面向接口的,像COM或Corba一樣,但是更加輕量級,它是使用代理類在客戶端和服務器端傳遞數據。實現AIDL的第一步就是了解並創建.aidl文件的規則和原理:首先在客戶端和服務器端的項目中創建後綴名爲.aildl的 同名文件(例如MyIPerson.aidl),並使用類java語言的編程方式在文件中編輯定義一個接口(interface
MyIPerson),保存後Android SDK 會自動在各自的項目/gen文件中創建一個MyIPerson.java文件,且該類包含了一個繼承了Binder類的內部類Stub,而我們知道Android進程間通信是基於IBinder實現的,由此我們可知客戶端便是藉助Stub來執完成進程間訪問的。
在MyIPerson.aidl文件中定義接口後,我們可以在接口中定義遠程客戶端需使用要的方法,定義的方法不需要實現,並且方法即可以有返回值、有參數(甚至其它AIDL定義的接口),也可以無返回值、無參數,但有一點需要注意的是接口中不能定義具有static
fields(全局變量)的值。
默認情況下,AIDL支持諸多的數據類型:除了八種基本數據類型(byte,short,int,long,char,float,double,boolean)和String、CharSequence、List和Map在.aidl中使用時無需import該類型具體路徑外,其它的數據類型都要使用import說明數據對象的包路徑,甚至在同一包中定義的interface。還有要注意的是所有非基本數據類型在接口定義時都需要說明數據的走向,使用in、on或者是inout,而基本數據類型默認的是in。
使用IPC傳遞一個對象
如果需要使用IPC(進程間通信)將一個類對象從一個進程傳遞到另一個進程中,那麼必須要保證該類能通過進程通道,在Java Web開發中我們一般可以讓類實現java.io.Serializable接口來實現序列化和反序列化操作進而進行跨進程的傳輸。而在Android開發中,Google爲開發者提供了一個序列化速度更快、更完善的Parcelable接口,它能更加方便地完成類的序列化和反序列化操作。接下來,我們用一個簡單的實例來說明Parcelable接口的用法,而一般使用Parcelable分爲以下幾個步驟:
<1>讓類實現Parcelable接口;
<2>重寫writeToParcel()和定義readFromParcel()方法,目的是得到當前對象的狀態並將其寫進Parcel中。
<3>實現Parcelable.creator接口並實例化,保證該對象爲static final類型且對象名命名爲CREATOR。
<4>最後,創建一個實現了Parcelable 接口的類的.aidl聲明文件。
下面有一個實例,具體代碼如下:
- package com.fendou.domain;
- import android.os.Parcel;
- import android.os.Parcelable;
-
-
- public class Person implements Parcelable {
-
- private int id ;
- private String userName;
- private double height ;
-
- public Person(){
-
- }
-
- private Person(Parcel in)
- {
- readFromParcel(in);
- }
-
- private static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
-
- public Person createFromParcel(Parcel source) {
-
- return new Person(source);
- }
-
- public Person[] newArray(int size) {
-
- return new Person[size];
- }
- };
- public int describeContents() {
-
- return 0;
- }
-
-
-
-
- public void writeToParcel(Parcel out, int arg1) {
-
- out.writeInt( id);
- out.writeString( userName);
- out.writeDouble( height);
-
- }
-
- public void readFromParcel(Parcel in){
- id = in.readInt();
- userName = in.readString();
- height = in.readDouble();
- }
- public int getId() {
- return id ;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getUserName() {
- return userName ;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public double getHeight() {
- return height ;
- }
-
- public void setHeight(double height) {
- this.height = height;
- }
-
- }
定義的Person聲明文件Person.aidl文件代碼如下:
- package com.fendou.aidl;
- parcelable Person;
AIDL實例講解
接下來,我們將通過一個實例來進一步瞭解AIDL的原理和使用方法。再此之前,我們先來參考一下Google官方提供給我們使用AIDL實現進程間通信的方法步驟,如下:
<1>創建後綴名爲.aidl文件;
<2>創建一個實現IBinder接口的類(Eclipse會自動完成);
<3>實例化一個ServiceConnection對象;
<4>調用Context.bindService()方法,並將IBinder對象通過onBind()方法返回給客戶端。
<5>在客戶端ServiceConnection中的onServiceConnection()方法中獲接收IBinder對象,並調用Stub對象中的asInterface()方法得到實例;
<6>在客戶端調用接口中定義的方法,一般在不能正常連接時會拋DeadObjectException異常。
<7>調用Context.unbindService()方法解除Service綁定。
根據以上提供的步驟,我們首先創建兩個項目,一個作爲客戶端,另一個座位服務器端,客戶端通過AIDL來操作服務器端的方法,實現進程間的通信。下面是兩個項目的目錄結構:
客戶端目錄結構:
客戶端MainActivity.java的源碼如下:
客戶端項目目錄包中的Person.java文件中的代碼上面已經列出,要實現遠程傳遞Person類對象,Person類需實現Parcelable接口。而MyIPerson.aidl是客戶端需要從服務端端調用的方法的接口設計文件,客戶端和服務端都需要定義該文件,以便建立起兩者之間的通信。而Person.aidl則是對Person.java的聲明,這個有點像C語言中的頭文件,客戶端和服務端同樣也都需要定義該文件。
下面再看服務端的目錄結構和代碼:
服務器端目錄結構:
服務端目錄包中MainService.java文件中的代碼如下:
- package com.fendou.service;
-
- import com.fendou.aidl.MyIPerson;
- import com.fendou.aidl.Person;
-
- import android.app.Service;
- import android.content.ComponentName;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.util.Log;
-
- public class MainService extends Service{
-
- private static final String TAG = "ygh";
-
-
-
- MyIPerson.Stub binder = new MyIPerson.Stub() {
-
- @Override
- public String getPerson(com.fendou.aidl.Person mPerson)
- throws RemoteException {
-
- String userInfo = "Hello," + mPerson.getUserName() +" !Welcome to study Android.\nYou ID is "+
- mPerson.getId() + ",height is "+ mPerson.getHeight() + "." ;
- return userInfo;
- }
- };
-
- @Override
- public void onCreate() {
-
- super.onCreate();
- Log. i(TAG, "onCreate");
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
-
- Log. i(TAG, "onStartCommand");
- return super .onStartCommand(intent, flags, startId);
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
-
- Log. i(TAG, "onStart");
- super.onStart(intent, startId);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
-
- Log. i(TAG, "onBind");
- return binder ;
- }
- @Override
- public boolean onUnbind(Intent intent) {
-
- Log. i(TAG, "onUnbind");
- return super .onUnbind(intent);
- }
-
- @Override
- public void onDestroy() {
-
- Log. i(TAG, "onDestroy");
- super.onDestroy();
- }
- }
項目中com.fendou.aidl包中的文件與客戶端項目中的同名文件是一樣的,其中Person.java和Person.aidl的代碼已列出,MyIPerson.aidl的內容如下:
- package com.fendou.aidl;
- import com.fendou.aidl.Person;
-
- interface MyIPerson{
- String getPerson(in Person mPerson);
- }
aidl文件中的接口和方法與java的很相似,只不過聲明方法時不需要定義修飾符(private、protected和public)。本實例是在接口中定義一個getPerson()方法,客戶端遠程調用Service中的該方法時,傳遞一個Person對象,該方法接收到對象後經過相關處理再返回一個String類型的值給客戶端。
下面我將稍微分析一下代碼的執行過程,首先運行服務端項目,由於服務端沒有定義Activity,因此不具備服務端沒有界面顯示,然後再運行客戶端項目,運行後的界面顯示如下圖:
首先點擊"Bound Service"按鈕,調用客戶端MainActivity中的boundService()方法,開啓遠程綁定服務,控制檯打印的Log信息如下:
說明開啓遠程綁定服務成功,並且在ServiceConnection中使用MyIPerson.Stub.asInterface(service)方法得到MyIPerson對象,然後再點擊“Call The method
from service”按鈕,實例化一個Person對象並遠程調用Service中的getPerson()方法,將Person對象傳遞作爲實參傳遞給該方法,然後經過方法處理返回一個String類型的值並使用TextView展現在界面上,界面效果如下圖:
由此我們可知客戶端遠程調用Service中的方法成功,使用AIDL完成了進程間的通信。最後,點擊“UnBound Service”解除Service綁定,控制檯打印的Log信息如下:
總結:以上實例簡單的使用了AIDL實現了進程間的通信,不過在AIDL實際應用中,遇到的情況可能會複雜得多,比如在Service中的方法操作會是一個較爲耗時的工作(比如文網絡下載、文件操作等),那麼我們就應避免在主線程中調用該Service中的方法,否則容易導致界面出現ANR,所以最好要考慮線程的安全性,我們可以將耗時操作放在一個新建的線程中執行。
源代碼下載,請戳這裏!