AIDL(Android interface definition lanuage),安卓接口定義語言。是一種android內部進程通信的描述語言。
AIDL支持的數據類型
1、基本數據類型
2、String和Charsequence
3、List,只支持ArrayList,其中的對象必須序列化
4、Map、只支持HashMap,其中對象必須序列化
5、Parcelable對象
6、AIDL接口
Messenger是以串行的方式處理客戶端發來的信息,如果大量的信息同時發送至服務端,服務端仍然只能逐個處理,所以如果有大量的並發請求,再用Messenger實現就不大合適了。並且使用Messenger無法調用服務端的方法。此時,AIDL是更好的實現IPC的方法。
使用AIDL的流程
1,服務端
服務端首先創建一個Service用於監聽客戶端的連接請求,然后創建一個AIDL文件,將暴露給客戶端的接口在AIDL文件中聲明,最后在Service中實現這個AIDL接口。
2、客戶端
綁定服務端的Service,綁定成功后,將返回的Binder對象轉換程AIDL接口所屬的類型。
具體實現
1、AIDL接口的創建
在Androidstudio中,可直接在project視圖下的src—>main目錄下新建AIDL文件會自動創建AIDL文件夾以及對應的AIDL文件。
一個聲明自定義接口的AIDL文件(其中IOnNewBookArrivedListener為監聽回調接口,用於服務端回調客戶端方法)
// IBookManager.aidl package com.example.aidltest; // Declare any non-default types here with import statements import com.example.aidltest.Book; import com.example.aidltest.IOnNewBookArrivedListener; interface IBookManager { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ List<Book> getBookList(); void addBook(in Book book); void registerNewBookListener(IOnNewBookArrivedListener listener); void unregisterNewBookListener(IOnNewBookArrivedListener listener); }
除此之外對於AIDL中所使用的Parcelable對象也必須創建同名AIDL文件並聲明序列化
// Book.aidl package com.example.aidltest; // Declare any non-default types here with import statements parcelable Book;
在AIDL文件中,即便是處於同一包下,使用時仍需要聲明。
序列化對象應存放在同名的java目錄下的包中,而不是AIDL下,否則將編譯錯誤(找不到java文件)。
聲明AIDL文件后,編譯,會自動生成接口對應的java文件。
AIDL的包結構在服務端以及客戶端必須保持一致,否則運行會發生錯誤。
2、遠程服務端Service的實現
創建Binder對象並在onBind方法中返回,該對象繼承自AIDL接口的Stub並實現其內部聲明的AIDL方法。
private Binder mBinder = new IBookManager.Stub(){ //實現接口方法 }
在AIDL方法中需處理線程同步,如對於ArrayList的並發讀/寫,可用線程同步的CopyOnWriteArrayList實現。(類似的還有ConcurrentHashMap),雖然AIDL只支持ArrayList,但在這里使用CopyOnwriteArrayList是可以的,因為Binder在訪問該對象時,是以List的規范訪問的,並最終形成一個新的
ArrayList對象發送給客戶端。
3、客戶端的實現
綁定服務,在ServiceConnection對象中的onServiceConnected方法中獲得AIDL接口對象。並通過該接口調用服務端的遠程方法即可。(service為服務端發送的Binder對象)
IBookManager bookManager = IBookManager.Stub.asInterface(service);
為了使服務端也能夠調用客戶端的方法,再聲明一個回調接口AIDL文件,方法類似。
1、遠程服務端的改進
在遠程服務端創建監聽器集合,用來保存不同客戶端的監聽器對象。在服務端的AIDL接口中新增監聽接口注冊方法,用於客戶端向服務端注冊接口。
由於Binder傳送的序列化對象實際上為不同的兩個對象,所以不能通過普通的方法來刪除監聽接口集合中的特定監聽接口。為解決這個問題,Android提供了專門的RemoteCallbackList用於保存及刪除監聽接口,在注冊和取消注冊時只需分別調用register和unregister方法並傳入監聽器對象即可。
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
遍歷監聽器集合時先調用beginBroadcast得到集合長度,在循環遍歷調用getBroadcastItem(i)取出對象,最后必須調用finishBroadcast方法。
private void onNewBookArrived(Book book)throws RemoteException{ mBookList.add(book); for (int i=0; i < mListenerList.beginBroadcast();i++){ IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i); if (listener != null){ listener.onNewBookArrived(book); } } mListenerList.finishBroadcast(); }
在客戶端調用AIDL接口方法中的注冊監聽監聽方法,向服務端添加監聽接口。 隨后服務端即可通過監聽接口調用客戶端的方法。
以上基本完成了簡單的AIDL應用。需注意的是被調用的方法是運行在實現接口的進程的Binder線程池中的,並且在執行方法時,調用方的線程會被掛起,所以應注意避免耗時操作或者在非UI線程中調用方法。同樣的也應避免在接口方法中訪問UI相關內容,若需要則通過Handle切換至UI線程。
Binder是有可能意外死亡的,所以當服務意外停止時,應重新連接服務。有兩種方法。
第一種,給Binder設置DeathRecipient監聽,當Binder死亡時會厚道binderDied方法回掉,在該方法中重新連接服務。
第二種,在onServiceDIsconnection中重新連接。
兩種方法的區別主要在,一個運行在Binder線程池中,另一個運行在UI線程中。
為AIDL的訪問設置權限限制,同樣有兩種方法
第一種,在AndroidManifest中聲明自定權限,在onBind方法增加權限判斷(checkCallingOrSelfPermission方法),不通過則返回null
第二種,在服務端的onTransact方法中進行權限驗證,失敗則返回false。
除了上述使用權限判斷的方法之外,還可以采用Uid和Pid來做驗證,getCallingUid和getCallingPid可以拿的客戶端所屬應用的Uid和Pid。並以此進一步獲得包名等信息。
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0) packageName = packages[0]; if (!packageName.startsWith("/*包名前綴*/")) return false; return super.onTransact(code, data, reply, flags); }