天天看點

Android--程序間通信(Binder)

Android系統提供了一些通用服務,比如音樂打電話發短信,WIFI,定位,輸入法,傳感器等。應用程式與這些通用服務運作在不同的程序中,如果應用程式想要與這些通用服務互動就要涉及到程序間通信,Binder就是為了Android程序間通信而設計的。

Binder架構

Binder是一種架構,這種架構提供了服務端接口、Binder驅動、用戶端接口三個子產品。

服務端

Binder服務端相當于一個Binder類對象。當建立該對象時,其内部會啟動一個線程不斷接收Binder驅動發送的消息。收到消息後會執行Binder.onTransact()方法。是以要實作一個Binder服務就必須重載onTransact()方法。

onTransact()方法通常用來做資料格式轉換,按約定的順序取出Binder用戶端發送來的資料并轉換成服務端識别的資料格式。

Binder驅動

Binder驅動運作在核心态,其所有操作都是基于記憶體而非硬體,用戶端與服務端通信時需要Binder驅動進行中轉。

當一個服務端Binder被建立時其在Binder驅動中同時會建立一個mRemote引用指向服務端。用戶端要通路服務端時首先要擷取服務端在Binder中的引用mRemote,擷取引用後就可以通過mRemote.transact()方法向服務端發送消息。

transact()方法實作了以下幾個功能:

  • 以線程間消息通信的模式向服務端發送用戶端傳遞過來的參數。
  • 挂起目前用戶端線程,等待服務端線程執行完畢後的通知(notify)
  • 接收服務端線程通知,繼續執行用戶端線程,并傳回執行結果

用戶端

這裡就是指我們需要和系統服務互動的應用程式。應用程式使用startService()與應用程式Service建立連接配接,使用getSystemService()與系統Service建立連接配接,進而進行通信。

資料格式

在進行IPC通信時需要約定用戶端與服務端通信的資料格式。Android使用AIDL(Android Interface Definition Language)約定資料格式。

Android提供了一個AIDL工具,可以把AIDL檔案轉換成一個Java類,在該Java類中同時重載了onTransact()方法和transact()方法,統一了存入和讀取參數。

實作Binder服務端

public class MusicService extends Binder{
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
        return super.onTransact(code,data,reply, flags);
    }
    
    public void start(String filePath){

    }
    public void stop(){}
}
                

以上代碼基于Binder建立了一個Service,使用startService()方法啟動後就可以看到DDMS中多了一個線程。

重載onTransact(int code, Parcel data, Parcel reply, int flags)方法:

switch(code){
     code 1000:
            data.enforceInterface("MusicService");// 資料校驗與用戶端writeInterfaceToken()對應
            String filePath = data.readString();//讀取一個字元串
            start(filePath);
            // reply.writeXXX();//如果需要傳回結果則寫入reply中
            break;
}
                

data中儲存着用戶端傳遞過來的參數,onTransact()方法内部需要從data中讀取用戶端傳遞的參數。參數的位置格式需要在AIDL檔案中限制。

reply中儲存服務端傳回用戶端的結果,如果需要傳回則調用Parcel提供的相關方法寫入結果。參數的位置格式需要在AIDL檔案中限制。

code變量辨別用戶端希望服務端執行哪個方法,這裡假定1000執行start()方法。

flags表示IPC調用模式:0代表"雙向模式",服務端在執行完指定服務後會傳回資料;1代表"單向模式",服務端在執行完指定服務後不反回任何資料。

實作Binder用戶端

首先在startService(),getSystemService()中擷取服務端Binder的引用,然後将參數按AIDL定義的格式寫入data中并調用mRemote.transact()方法傳入參數,服務端會在onTransact()方法中取出這裡出入的參數:

IBinder mRemote = null;
String filePath = "/sdcard/music/xxx.mp3";
int code = 1000;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("MusicService");
data.writeString(filePath);
mRemote.transact(code,data,reply,0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
                

調用mRemote.transact()方法後,Binder驅動會挂起目前用戶端線程,并向服務端發送一個消息,這個消息就包括用戶端傳遞的參數。服務端接收到消息,解析資料執行完相關任務後會把執行結果寫入reply中,然後向Binder驅動發送一個notify消息,Binder驅動從挂起處喚醒用戶端線程繼續執行。

系統Service與應用程式Service

Service也就是服務分為兩種,一種是系統建立的Service,另一種是應用程式自定義的Service。這兩種Service都是Binder服務端。

系統Service

系統Service在系統系統初始化時從SystemServer程序中啟動。常見的有PowerManagerService(電源管理服務),VibratorService(振動傳感器服務),WindowManagerService(視窗管理服務)NotificationManagerService等等,每個系統服務都是一個Binder,都運作在一個獨立的線程中。

系統提供了一個ServiceManager類來管理系統服務ServiceManager本身也是一個Binder,運作在一個獨立的程序中,其提供了一個全局Binder供應用程式使用,應用程式擷取其他系統服務都是通過ServiceManager的全局Binder來擷取的。

這設計的好處僅僅暴露一個全局Binder引用,其他系統服務則隐藏起來,進而有助于系統擴充,以及調用系統服務的安全檢查。

系統Service在啟動時首先向ServiceManager注冊Service(注冊自己),當調用getSystemService(serviceName)擷取系統服務時,會間接調用到ServiceManager.getService(String name)方法。getService()實作如下所示:

public static IBinder getService(String name){
    try{
        IBinder service = sCache.get(name);
        if(service != null){
            return service;
        }else{
            return getIServiceManager().getService(name);
        }
    } catch(RemoteException e){
        Log.e(TAG,"error in get Service", e);
    }
}
                

首先從緩存中擷取,沒擷取到則從getIServiceManager()中擷取,getIServiceManager()傳回的是系統唯一ServiceManager的Binder。

應用程式Service

系統在ActivityManagerService中提供了startService()方法來啟動應用程式自定義Service。用戶端使用以下方法和應用程式Service建立連接配接:

public ComponentName startService(Intent intent)
public boolean bindService(Intent service, ServiceConnection conn,int flags)
                

startService用于啟動一個服務,bindService用于綁定一個服務,bindService的第二個參數是擷取服務端Binder的關鍵所在:

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

onServiceConnected()方法的第二個變量就是我們需要的服務端Binder的引用,當用戶端請求AmS啟動某個Service,如果該Service正常啟動,那麼AmS就會遠端調用ActivityThread類中的ApplicationThread對象,調用的參數包括Service的Binder引用,然後在ApplicationThread中會回調bindService()方法中的conn接口。是以,在用戶端中,可以在onServiceConnected()方法中将其參數Service儲存為一個全局變量,進而在用戶端任何地方都可以随時調用該服務。

作者:Gooooood

連結:https://www.jianshu.com/p/ef69025eb7f3

來源:簡書

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

繼續閱讀