IPC機制——使用AIDL


  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);
        }

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM