從Android develop文檔複製而來,只爲自己閱讀方便,翻牆不易。
Android的接口定義語言(AIDL)
AIDL(Android界面定義語言)相似,你可能已經使用過其他的IDL。它允許您定義的編程接口,客戶端和服務,才能與對方使用進程間通信(IPC)進行通信商定。在Android上,一個進程無法正常訪問另一個進程的內存。所以講,他們需要自己的對象分解成原語操作系統能夠理解,馬歇爾跨邊界的對象爲您服務。要做到這一點編組的代碼是繁瑣的編寫,因此Android與AIDL爲您代勞。
注意:使用AIDL是必要的前提是你讓來自不同應用程序的客戶端訪問服務的IPC,並希望在處理你的服務多線程。如果你不需要在不同的應用程序執行併發IPC,你應該創建界面實現活頁夾,或者,如果要執行IPC,但並不需要處理多線程,實現你的界面使用Messenger的。無論如何,請確保您瞭解綁定服務實現一個AIDL之前。
在你開始設計AIDL接口,要知道是直接的函數調用,調用一個AIDL接口。你不應該對其中調用發生線程的假設。根據呼叫是否來自本地進程中的線程或遠程進程會發生什麼事是不同的。特別:
- 從本地工藝製成的呼叫在正在呼叫的同一個線程執行。如果這是你的主UI線程,該線程將繼續在AIDL接口來執行。如果它是另一個線程,即,在服務執行代碼中的之一。因此,如果只是本地線程訪問該服務,可以完全哪些線程中執行它(但如果是這樣的話,那麼你不應該在所有使用AIDL控制,但應改爲創建接口實現一個活頁夾)。
- 從遠程進程調用從一個線程池的平臺,自己的過程內保持調度。必須從未知線程來電,具有多個呼叫同時發生來製備。換句話說,一個AIDL接口的實現必須完全線程安全。
- 該
單向
關鍵字改變遠程調用的行爲。在使用時,一個遠程調用不會阻塞; 它只是發送的交易數據,並立即返回。的接口的實現最終接收該從常規呼叫粘合劑
線程池作爲正常遠程調用。如果單向
與本地電話使用的,不存在影響和通話仍然是同步的。
定義一個AIDL接口
您必須確定您的AIDL接口在.aidl
使用Java編程語言的語法文件,然後將其保存在源代碼(在SRC /
目錄下)這兩個應用託管服務,並結合該服務的任何其他應用程序。
當你建立一個包含每個應用程序.aidl
文件時,Android SDK工具生成的IBinder
基於接口.aidl
文件並將其保存在項目的根/
目錄下。該服務必須實現的IBinder
接口爲宜。然後,客戶端應用程序綁定到從服務和調用方法的IBinder
執行IPC。
要使用AIDL有界服務,請按照下列步驟操作:
- 創建.aidl文件
該文件定義了方法簽名的編程接口。
- 實現接口
在Android SDK工具生成的Java編程語言的界面,根據您的
.aidl
文件。這個接口有一個內部抽象類名爲存根
擴展粘結劑
並實現從你的AIDL接口中的方法。您必須擴展存根
類,並實現方法。 - 公開接口給客戶
注意:您對您的AIDL接口的先放後的任何修改必須保留,以避免破壞使用你的服務的其他應用程序向後兼容。也就是說,因爲你.aidl
文件必須爲了他們訪問您的服務的接口,你必須保持原有的接口支持被複制到其他應用程序。
1.創建.aidl文件
AIDL使用一個簡單的語法,它允許您聲明一個接口,可以採取參數和返回值的一種或多種方法。的參數和返回值可以是任何類型的,即使是其他的AIDL生成接口。
您必須構建.aidl
使用Java編程語言文件。每個.aidl
文件必須定義一個接口,只需要接口聲明和方法簽名。
默認情況下,AIDL支持以下數據類型:
- 所有原始類型的Java編程語言(如
INT
,長
,焦炭
,布爾
,等等) 串
爲CharSequence
名單
在所有的元素
列表
必須在此列表中支持的數據類型之一,或者您已經聲明瞭其他AIDL生成的接口或Parcelables並之一。一個列表
可以選擇作爲“通用”類(例如,名單<String>的
)。認爲對方收到實際的具體類始終是一個ArrayList中
,雖然會產生使用方法列表
界面。地圖
在所有元素
地圖
必須在此列表中支持的數據類型之一,或者您已經聲明瞭其他AIDL生成的接口或Parcelables並之一。通用圖,(如那些形式的地圖<字符串,整數>
不被支持。認爲對方收到實際的具體類始終是一個HashMap中
,雖然會產生使用方法地圖
界面。
您必須包括進口
以上未列出的其他每個類型的語句,即使他們在同一個包爲你的接口定義。
在定義服務接口,請注意:
- 方法可以採取零個或多個參數,並返回一個值或空隙。
- 所有非基本參數要求定向標記指示數據去哪個方向。無論是
在
,出
,或INOUT
(見下面的例子)。原語是
在
默認情況下,不能以其他方式。注意:您應該限制的方向是什麼真正需要的,因爲編組參數是昂貴的。
- 包括在所有代碼的註釋
.aidl
文件都包含在生成的IBinder
接口(除進口和包語句之前的評論)。 - 只有方法的支持; 你不能揭露AIDL靜態字段。
下面是一個例子.aidl
文件:
// IRemoteService.aidl 包融爲一體。例如,機器人; //此處聲明任何非默認類型的import語句 / **示例服務接口* / 接口 IRemoteService { / **要求這項服務的進程ID,做壞事用它。* / INT GETPID (); / **表明可以作爲參數使用一些基本類型 。*在AIDL返回值 * / 無效basicTypes (INT ANINT , 長一起, 布爾aBoolean , 漂浮水上, 雙aDouble , 字符串ASTRING ) ; }
只需保存.aidl
文件在項目的的src /
目錄下,當你建立你的應用程序,SDK工具生成的IBinder
在項目的接口文件根/
目錄下。所生成的文件名
相匹配的.aidl
文件名 ,但是具有的.java
擴展名(例如,IRemoteService.aidl
導致IRemoteService.java
)。
如果你使用Android工作室,增量構建幾乎立即產生粘合劑類。如果你不使用Android工作室,那麼搖籃工具生成構建應用程序,你應該建立與項目粘合劑類下一次gradle這個assembleDebug
(或gradle這個assembleRelease
只要你寫完).aidl
文件,所以你的代碼可以對鏈接生成的類。
2.實現接口
當你建立你的應用程序時,Android SDK工具生成的.java
您的名字命名的接口文件.aidl
文件。生成的接口包括一個名爲子類存根
這是一個抽象實現其父接口(例如,YourInterface.Stub
),並聲明所有從方法.aidl
文件。
注: 存根
還定義了一些輔助方法,最值得注意的是asInterface()
,它接受一個的IBinder
(通常是一個傳遞給客戶的onServiceConnected()
回調方法)並返回存根接口的一個實例。請參見調用一個IPC方法爲如何使這個轉換的更多細節。
爲了實現從所產生的界面.aidl
,延長所生成的粘合劑
的接口(例如,YourInterface.Stub
)並實施從繼承的方法.aidl
文件。
這裏是稱爲界面的示例實施IRemoteService
(由定義的 IRemoteService.aidl
使用匿名實例例如,上文):
private final IRemoteService . Stub mBinder = new IRemoteService . Stub () { public int getPid (){ return Process . myPid (); } public void basicTypes ( int anInt , long aLong , boolean aBoolean , float aFloat , double aDouble , String aString ) { //不執行任何操作 } };
現在mBinder
是的一個實例存根
類(粘合劑
),其定義了服務的RPC接口。在下一步驟中,這種情況下被暴露於客戶端,這樣他們可以與服務進行交互。
還有,你應該知道你的實現AIDL接口時,一些規則:
- 呼入電話無法保證會在主線程上執行的,所以你需要考慮從一開始多線程和正確建立服務是線程安全的。
- 默認情況下,RPC調用是同步的。如果您知道該服務需要超過幾毫秒的時間來完成一個請求,你不應該從活動的主線程中調用它,因爲它可能會掛起應用(Android版可能會顯示一個“應用程序沒有響應”對話框) - 你應該通常在客戶端一個單獨的線程調用它們。
- 沒有你拋出異常發送回調用者。
3.公開接口給客戶
一旦你實現的接口爲您服務,您需要將其暴露給客戶,使他們能夠綁定到它。揭露爲您服務的接口,擴展服務
並實現onBind()
返回類實現所產生的一個實例存根
(如上一節中討論)。下面是一個公開的例子服務IRemoteService
例如接口給客戶。
返回 不執行任何操作 } }; }
現在,當一個客戶端(如活動)調用bindService()
來連接到該服務,客戶端的onServiceConnected()
回調接收 mBinder
由服務的返回的實例onBind()
方法。
客戶端還必須能夠訪問的接口類,因此,如果在客戶端和服務都在獨立的應用程序,則在客戶端的應用程序必須具有的副本.aidl
在其文件的src /
目錄(它產生android.os.Binder
接口-提供給AIDL方法,客戶端訪問)。
當客戶端收到的IBinder
在onServiceConnected()
回調,它必須調用 YourServiceInterface .Stub.asInterface(服務)
將返回參數轉換爲YourServiceInterface
類型。例如:
IRemoteService mIRemoteService ; 私人 ServiceConnection mConnection = 新 ServiceConnection () { //當與服務建立連接調用 公共 無效onServiceConnected (組件名的className , 的IBinder 服務) { //按照上面一個AIDL接口的例子, //這得 到一個在IRemoteInterface,我們可以用它來 在服務調用的實例 mIRemoteService = IRemoteService 。存根。asInterface (服務) } //調用時與服務的連接意外斷開 公共 無效onServiceDisconnected (組件名的className ) { 日誌。Ë (TAG , “服務意外中斷” ); mIRemoteService = 空; } };
欲瞭解更多示例代碼,請參見RemoteService.java
類ApiDemos。
傳遞對象超過IPC
如果你有,你想通過IPC接口從一個進程發送到另一個類,你可以做到這一點。但是,你必須確保你的類的代碼可到IPC通道的另一邊,你的類必須支持Parcelable
接口。支持Parcelable
接口很重要,因爲它可以讓Android系統分解物進入,可以跨進程編組元。
要創建支持類Parcelable
協議,必須做到以下幾點:
- 讓你的類實現
Parcelable
接口。 - 實施
writeToParcel
,這需要對象的當前狀態,並把它寫入到一個包
。 - 一個叫做靜態字段添加
CREATOR
到您的類,這是實現一個對象Parcelable.Creator
接口。 - 最後,創建一個
.aidl
聲明你parcelable類(如圖所示的文件Rect.aidl
文件,下同)。如果您使用的是自定義生成過程中,千萬不能添加
.aidl
文件到您的構建。類似於C語言的頭文件,這.aidl
文件不被編譯。
AIDL使用這些方法和字段在它生成於編組和解組的對象的代碼。
例如,這裏是一個Rect.aidl
文件來創建一個矩形
類的parcelable:
包裝機器人,圖形; //聲明矩形這樣AIDL可以找到它,知道它實現 //將parcelable協議。 parcelable 矩形;
這裏是該怎麼一個例子矩形
類實現 Parcelable
協議。
import android . os . Parcel ; import android . os . Parcelable ; public final class Rect implements Parcelable { public int left ; public int top ; public int right ; public int bottom ; public static final Parcelable . Creator < Rect > CREATOR = new Parcelable . Creator < Rect >() { public Rect createFromParcel ( Parcel in ) { return new Rect ( in ); } public Rect [] newArray ( int size ) { return new Rect [ size ]; } }; public Rect () { } private Rect ( Parcel in ) { readFromParcel ( in ); } public void writeToParcel ( Parcel out ) { out . writeInt ( left ); out . writeInt ( top ); out . writeInt ( right ); out . writeInt ( bottom ); } public void readFromParcel ( Parcel in ) { left = in . readInt (); top = in . readInt (); right = in . readInt (); bottom = in . readInt (); } }
在編組矩形
類是非常簡單的。看看在其他的方法包裹
看到其他類型的值,你可以寫一個包裹。
警告:不要忘記其他進程接收數據的安全問題。在這種情況下,矩形
讀取四個數字包裹
,但它是由你來確保,這些是任何呼叫者正試圖做的值的可接受的範圍內。見安全和權限有關如何保證應用程序的安全免受惡意軟件的更多信息。
調用一個IPC方法
這裏有一個調用的類必須調用與AIDL定義的遠程接口的步驟:
- 包括
.aidl
在項目文件中的src /
目錄下。 - 聲明的一個實例
的IBinder
接口(生成基於所述AIDL)。 - 實施
ServiceConnection
。 - 呼叫
Context.bindService()
,並傳入您的ServiceConnection
實現。 - 在你執行
onServiceConnected()
,您將收到的IBinder
實例(稱爲服務
)。呼叫YourInterfaceName .Stub.asInterface((的IBinder)服務)
將返回的參數強制轉換爲YourInterface類型。 - 請致電您在接口上定義的方法。你應該總是陷阱
DeadObjectException
異常,當連接中斷而拋出; 這將是由遠程方法拋出的唯一例外。 - 要斷開連接,調用
Context.unbindService()
與接口的實例。
在調用一個IPC服務需要注意幾點:
- 對象引用跨進程計數。
- 您可以發送匿名對象作爲方法的參數。
有關綁定到服務的更多信息,請閱讀綁定服務 文檔。
下面是一些示例代碼演示調用一個AIDL創建的服務,從項目ApiDemos遠程服務取試樣。
公共 靜態 類 綁定 擴展 活動 { / **主界面,我們將在服務調用進行。* / IRemoteService MSERVICE = 空; / **我們使用該服務的另一個接口。 本次活動的標準初始化。設置UI,然後等待 *爲用戶做之前,它捅 留意按鈕 附“ ); } / ** *分類爲與所述主界面交互 這與該服務的連接已被調用時 //建立,使我們的服務對象,我們可以用它來 //使用服務進行交互。我們正在與我們溝通 ,通過IDL接口//服務,因此得到了客戶端 //從原始服務的代表 我們想監控,只要我們的服務 //連接到它。 嘗試 { MSERVICE 。registerCallback (mCallback ); } 趕上 (RemoteException的ē ) { //在這種情況下,我們可能甚至在服務已經崩潰 //做與任何東西,我們可以指望很快被 //斷開(然後重新連接,如果可以重新啓動) //所以沒有必要做任何事情在這裏。 } //由於樣本的一部分,告訴用戶什麼 這就是所謂的當與服務的連接已 //意外斷開-也就是說,它的進程崩潰。 MSERVICE = 空; mKillButton 。的setEnabled (假); mCallbackText 。的setText (“斷開” ); //作爲一部分樣本,告訴用戶什麼 類具有的二級界面進行交互 連接到輔助接口是相同的任何 //其他 與該服務建立了幾個連接,結合 由接口名稱//這允許其它應用程序是 //裝通過實施替換遠程服務 //相同 如果我們收到了該服務,因此與註冊 //它,那麼現在是註銷的時間 ,如果 (MSERVICE != 空) { 嘗試 { MSERVICE 。unregisterCallback (mCallback ;) } 趕上 (RemoteException的ē ) { //有沒什麼特別的,我們需要做的,如果服務 //已崩潰。 } } //分離我們現有的 殺死進程託管我們的服務,我們需要知道它 // PID。我們的便利服務,具有呼叫,將返回 //給我們的信息。 如果 (mSecondaryService != 空) { 嘗試 { INT PID = mSecondaryService 。GETPID ( ); //需要注意的是,雖然該API允許我們請求 //殺根據它的PID任何進程,內核會 //仍然徵收標準限制它的PID你 //居然能夠殺死通常,這意味着只。 //運行應用程序和任何其他的過程 由應用程序創建//流程如下圖所示;包 //共用一個UID也能自相殘殺 。//其他的流程 過程。killProcess (PID ); mCallbackText 。的setText (“殺了服務流程。” ); } 趕上 (RemoteException的前) { //從託管過程中恢復正常 。//服務器垂死 //只爲樣本的目的,提出了一個 -------------------------------------------------- -------------------- //顯示如何處理回調代碼。 // ------------------ -------------------------------------------------- - / ** *這種實現用於從遠程接收回調 *服務。 * / 私人 IRemoteServiceCallback mCallback = 新 IRemoteServiceCallback 。存根() { / ** *這是由遠程服務名爲定期向我們講述 *新。值的注意的是IPC調用都是通過一個線程進行調度 的每個進程中運行*池,所以這裏執行的代碼將 *不是我們最喜歡的其他東西主線程中運行-因此, *更新UI,我們需要使用一個Handler來躍過 從服務:“ + 味精。ARG1 ); 打破; 默認: 超。的handleMessage (味精); } } }; }