Android中的Binder學習筆記

本文內容是我從《Android內核剖析》一書中學習整理。以便以後複習時方便查閱。

一、Binder
1.關於Binder
1.1 Binder是一種架構,這種架構提供了服務器接口、Binder驅動、客戶端接口三個模塊。

1.2 一個Binder服務器實際上就是一個Binder類的對象,該對象一旦創建,內部就啓動一個隱藏線程,該線程接下來會接收Binder驅動發送的消息,接收到消息之後,會執行到Binder對象的onTransact()函數,並按照該函數的參數執行不同的服務代碼。因此,要實現一個Binder服務,必須重載onTransact()方法。

1.3 任意一個服務端Binder對象唄創建時,同時會在Binder驅動中創建一個mRemote對象,該對象的類型也是Binder類。客戶端要訪問遠程服務時,都是通過mRemote對象。

1.4 Binder框架
image
1.5 客戶端想要訪問遠程服務,必須獲取遠程服務在Binder對象中對應的mRemote引用。然後調用該引用的transact()方法,而在Binder驅動中,mRemote對象也重載了transact()方法。

2.設計Service端
2.1 繼承Binder類即可設計Service端,如下代碼:
代碼2-1-1設計Service示例

public class MusicPlayerService extends Binder{

      @Override
      protected boolean onTransact(int code,Parcel data,Parcel reply,int flags){
         switch(code){
             case1000:{
                 data.enforceInterface(“MusicPlayerService”);
                 String filePath=data.readString();//從包裹中取出一個字符串
                start(filePath);
                //replay.writeXXX();
               break;
            }
         }
         return super.onTransact(code,data,reply,flags);
      }

      publi cvoid start(String filePath){}
      public void stop(){}
}

2.2 當要啓動該服務時,只需初始化一個MusicPlayerService對象即可,如果在Activity裏面初始化一個MusicPlayerServeice,然後運行,會在DDMS中發現多了一個線程。

2.3 code變量用於表示客戶端期望調用服務端的那個函數,因此雙方約定一組int值,不同的值代表不同的服務器函數,該值與客戶端的transact()函數中第一個參數code的值是一致的。enforceInterface()是爲了某種校驗,它與客戶端的writeInterfaceToken()對應。

3 Binder客戶端設計
3.1transact()函數原型爲:

public final boolean transact(int code,Parcel data,Parcel reply,int flags);

data表示的是要傳遞給遠程Binder服務的包裹(Parcel),遠程服務函數所需要的參數必須放入這個包裹中。包裹中只能放入特定類型的變量如:String、int、long等。除了一般原子變量外,Parcel還提供了writeParcel()方法,因此,要進行Binder遠程服務調用時,服務函數的參數要麼是一個原子類,要麼必須繼承Parcel類。參數falsgs含義是執行IPC調用模式,分爲兩種:一種是雙向,用0表示,含義是服務端執行完成後會返回一定的數據;另一種是單向,用1表示,含義是不返回任何數據。

3.2 客戶端調用transact()方法:
代碼3-3-1客戶端調用transact()示例

IBinder mRemote=null;
String filePath=”/sdcard/music/heal_the_world.mp3”;
intcode = 1000;
Parcel data=Parcel.obtain();//包裹不是客戶端自己創建的,而是申請到的
data.writeInterfaceToken(“MusicPlayerService”);//標註遠程服務名稱,不是必需的
data.writeString(filePath);//向包裹中添加String,注意,添加的內容是有序的,約定好的
mRemote.transact(code,data,reply,0);
IBinder binder=reply.readStrongBinder();
reply.recycle();
data.recycle();

3.3 調用transact()方法後,客戶端線程進入Binder驅動,Binder驅動會掛起當前線程,並向遠程服務發送一個消息,消息中包含客戶端傳進來的包裹。

4 獲取Binder對象
4.1 手工編寫Binder服務端和客戶端的過程存在兩個重要問題

(1)客戶端如何獲得服務端的Binder對象引用
(2)客戶端和服務端必須事先約好兩件事情:
(a)服務端函數的參數在包裹中的順序
(b)服務端不同函數的int型標識

4.2Android的Service與Binder關係:

AmS提供了startService()函數用於啓動客戶服務,而對於客戶端來說,可以使用以下兩個函數來和一個服務建立連接,其原型在android.app.ContextImpl類中。

     public ComponentName startService(Intent intent);

該函數啓動服務後,客戶端暫時還沒有服務端的Binder引用,因此暫時還不能調用任何服務功能。

     public booleanbindService(Intent service,ServiceConnection conn,int flags);

該函數用於綁定一個服務,這就是第一個重要問題的關鍵所在。第二個參數是一個interface類,該interface的定義如下代碼:
代碼4-2-1ServiceConnection接口定義

publicinterface ServiceConnection{
    public void onServiceConnected(ComponentName name,IBinder service);
    public void onServiceDisconnected(ComponentName name);
}

注意該接口中的onServiceConnected()方法的第二個參數service。當客戶端請求AmS啓動某個Service後,該Service如果正常啓動,那麼AmS就會遠程調用ActivityThread類中的ApplicationThread對象,調用的參數中會包含Service的Binder引用,然後在ApplicationThread中會回調bindService中的conn接口。因此在客戶端中可以在onServiceConnected()方法中將其參數Service保存爲一個全局變量,從而在客戶端的任何地方都可以隨時隨地調用該遠程服務,這就解決了第一個問題,即客戶端如何獲取遠程服務的Binder引用。流程如下:
圖4.2.1Binder客戶端和服務端的調用過程
4.3 保證包裹內參數順序aidl工具的使用

關於第二個問題,Android的SDK中提供了一個aidl工具,該工具可以吧一個aidl文件轉換爲一個Java類文件,在該Java類文件,同時重載了transact和onTransact()方法,統一了存入包裹盒讀取包裹參數,從而使設計者可以吧注意力放到服務代碼本身上。

5.系統服務中的Binder對象
5.1ServiceManager是一個獨立進程,其作用是管理各種系統服務,管理的邏輯如圖所示:
image
5.2ServiceManager本身也是一個Service,Framework提供了一個系統函數,可以獲取該Service對應的Binder引用,那就是BinderInternal.getContextObject()。該靜態函數返回ServiceManager後,就可以通過ServiceManager提供的方法獲取其他系統Service的Binder的引用。其他系統服務在啓動時,首先把自己的Binder對象傳遞給ServiceManager,即所謂的註冊(addService)。

本文轉自Android中的Binder學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章