Android Service 之三(Bind Service,使用 AIDL)

前面寫了 Bind Service 的兩種實現方式,接下來轉一篇貼子來簡述一下最後一種實現方式
第三種:使用 AIDL

前面講的使用 Messenger 技術,其實是基於 AIDL架構的,但是 Messenger 使用一個隊列來處理所有的請求,這樣一來,就無法進行多線程的併發了。所以,如果你想同時接受並處理多個 client 的請求,那麼請使用 AIDL 來實現,但這樣的話,你需要小心地進行同步處理了哦~
默認情況下,一個應用不管有多少個 Activity、Service 或其他組件,它們都是運行在一個進程上,但是我們可以安排 Service 運行一個新的進程上,但是不同進程之間應該如何通信呢?當需要在不同的進程之間傳遞對象時,應該怎麼做呢?AIDL(Android Interface Definition Language) 便是解決這一問題的鑰匙。
使用 AIDL 並不是難事,但是比較繁瑣,並且一不小心容易出錯。好在 Android Dev Guide 的 "AIDL" 章節(在 "Dev Guide " 左側列表的最下面)對這個問題講解非常詳細,再結合 Android APIDemo 中的 Remote Service Binding 給出了的示例,這都給了開發者提供了非常到位的幫助。以下內容就是我結合二者,整理出來的筆記,力求真理,但能力有限,差錯難免,請讀者堅持自己的判斷力,本文只供參考,且不可盡信。

一、使用 AIDL 實現 IPC
面對問題,應統攬全局,歸納階段,劃定步驟,共得出四大步,分列如下:
① 創建.aidl 文件
    在這個文件裡定義 method 和 field
② 把 .aidl 文件加入到 makefile 中去
    如果使用 Eclipse 則 ADT 會幫你管理
③ 實現接口方法
    AIDL 編譯器會根據接口生成一個用 Java 語言編寫的interface,這個 interface 有一個抽象的內部類,名字為 Stub,你必須創建一個類,繼承於它,並且實現 .adil 文件中所聲明的方法
④ 公開接口給客戶端
    如果創建的是 service,則應該繼承自 Service,並且重載 Service.onBind() 返回實現接口的類的實例

這四個步驟在 Remote Service Binding 中均有所呈現,以下分開闡述。Remote Service Binding 共包含有兩個 .java 文件,三個 .aidl 文件,物理結構比較簡單,但是邏輯結構就不那麼簡單,以下用 Class Diagram 來展示其中的關係。



 

1、創建.aidl 文件
AIDL 語法簡單,用來聲明接口,其中的方法接收參數和返回值,但是參數和返回值的類型是有約束的,且有些類型是需要 import,另外一些則無需這樣做。
AIDL 支持的數據類型劃分為四類,第一類是 Java 編程語言中的基本類型,第二類包括 String、List、Map 和 CharSequence,第三類是其他 AIDL 生成的 interface,第四類是實現了 Parcelable 接口的自定義類。
其中,除了第一類外,其他三類在使用時均需要特別小心。
使用第二類時,首先需要明白這些類不需要 import,是內嵌的。其次注意在使用 List 和 Map 此二者容器類時,需注意其元素必須得是 AIDL 支持的數據類型,List 可支持泛型,但是 Map 不支持,同時另外一端負責接收的具體的類裡則必須是 ArrayList 和 HashMap。
使用第三、四類時,需要留意它們都是需要 import 的,但是前者傳遞時,傳遞的是 reference,而後者則是 value。

在創建 .aidl 文件的過程中,應該注意一旦 method 有參數,則需注意在前面加上 in, out 或 inout,它們被稱為 directional tag,但是對於基本類型的參數,默認就是 in,並且不能為其他值。

Remote Service Binding 共包括了三個 .aidl 文件,分別是IRemoteService.aidl、IRemoteServiceCallback.aidl、ISecondary.aidl,通過它們可以看到如何使用第一類和第三類的數據類型,稀罕的是,看不到第二類、第四類數據類型的使用,也沒有看到 directional tag。

 

2、實現 Interface
AIDL 為你生成一個 interface 文件,文件名稱和 .aidl 文件相同。如果使用 Eclipse 插件,則 AIDL 會在構建過程中自動運行,如果不使用插件,則需要先使用 AIDL。
生成的 interface 會包含一個抽象內部類 Stub,它聲明瞭在 .aidl 文件裡的所有方法。要想實現你在 .aidl 文件裡定義的接口,就必須實現這個Stub類,如下:

Java代碼   收藏代碼
  1. /** 
  2.  * This implementation is used to receive callbacks from the remote 
  3.  * service. 
  4.  */  
  5. private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {  
  6.     /** 
  7.      * This is called by the remote service regularly to tell us about 
  8.      * new values.  Note that IPC calls are dispatched through a thread 
  9.      * pool running in each process, so the code executing here will 
  10.      * NOT be running in our main thread like most other things -- so, 
  11.      * to update the UI, we need to use a Handler to hop over there. 
  12.      */  
  13.     public void valueChanged(int value) {  
  14.         mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));  
  15.     }  
  16. };  

 

Stub 也定義了一些幫助方法,比較常用的有 asInterface(),其接收一個 IBinder 作為參數,並且返回一個 interface 的實例用來調用IPC方法。

Java代碼   收藏代碼
  1. private static INotificationManager sService;  
  2.    
  3. static private INotificationManager getService()  
  4. {  
  5.     if (sService != null) {  
  6.         return sService;  
  7.     }  
  8.     sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));  
  9.     return sService;  
  10. }  

 

 要實現 interface,需要繼承 Stub,實現其方法,這在 RemoteService 和 RemoteServiceBinding 都可以找到相關代碼。

這個環節是重中之重,需要特別小心的有兩點,其一是拋出的所有異常均不會發給調用者;其二是IPC調用是同步的,這意味IPC服務一旦花費較長時間完成的話,就會引起ANR,應該將這樣的操作放在單獨的線程裡。

 

3、向客戶端公開 Interface
獨樂樂不如眾樂樂,需要將服務公開出去,要達成這個目的,須得創建一個 Service 的子類,並且實現 Service.onBind(Intent),通過這個方法將實現了接口的類的實例返回回來。通過查看 RemoteService 一目瞭然。

 

Java代碼   收藏代碼
  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     // Select the interface to return.  If your service only implements  
  4.     // a single interface, you can just return it here without checking  
  5.     // the Intent.  
  6.     if (IRemoteService.class.getName().equals(intent.getAction())) {  
  7.         return mBinder;  
  8.     }  
  9.     if (ISecondary.class.getName().equals(intent.getAction())) {  
  10.         return mSecondaryBinder;  
  11.     }  
  12.     return null;  
  13. }  

 

其中的 mBinder 和 mSecondaryBinder 分別是實現了 IRemoteService 和 ISecondary 接口的類的實例。

4、使用Parcelables傳值
前文中提到 Remote Servcie Binding 沒有使用第四類數據類型作為參數,這是示例的不足,要想讓一個類變成第四類,需要遵照以下步驟:

① 引入 Parcelable 接口
② 實現 writeToParcel(Parcel out)
③ 增加一個靜態的field,其實現 Parcelable.Creator 接口
④ 創建一個 .aidl 文件,用以聲明你的 parcelables 類
在 "AIDL" 中,類 Rect 是一個不錯的示例,彌補了 Remote Service Binding 的不足。

 

二、調用 IPC 方法
萬事俱備,只欠東風,IPC 備妥,只待調用。在 Remote Service Binding 中,RemoteServiceBinding 正是 IPC 的調用者,既然要使用接口,那就先聲明 interface 類型的變量

Java代碼   收藏代碼
  1. // The primary interface we will be calling on the service.   
  2. IRemoteService mService = null;   
  3. // Another interface we use on the service.   
  4. ISecondary mSecondaryService = null;   

 

實現 ServiceConnection,在 onServiceConnected(ComponentName className, IBinder service) 中完成對 mService 和 mSecondaryService 的賦值。

 

Java代碼   收藏代碼
  1. private ServiceConnection mConnection = new ServiceConnection() {  
  2.     public void onServiceConnected(ComponentName className,  
  3.             IBinder service) {  
  4.         mService = IRemoteService.Stub.asInterface(service);  
  5.         // ... 以下省略  
  6.     }  
  7. }  

 

接著別忘了調用 Context.bindService(),完成任務以後,調用 Context.unbindService()。如果在 connection 中斷的情況下,調用 IPC 方法,你會遇到 DeadObjectException,這是 remote method 能拋出的唯一異常。

 

原文地址: http://www.poemcode.net/2010/05/aidl-ipc/


书籍推荐