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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。