Android進階——Android跨程序通訊機制之Binder、IBinder、Parcel、AIDL
前言
Binder機制是Android系統提供的跨程序通訊機制,這篇文章開始會從Linux相關的基礎概念知識開始介紹,從基礎概念知識中引出Binder機制,歸納Binder機制與Linux系統的跨程序機制的優缺點,接著分析Binder的通訊模型和原理,而Binder機制最佳體現就是AIDL,所以在後面會分析AIDL的實現原理,最後簡單的提下AMS的Binder體系,整篇文章中間會穿插有IBinder、Binder、Parcel的介紹,整篇文章閱讀難度不大,不會涉及到framework層的Binder原理,AIDL部分需要有AIDL的使用基礎
基礎概念
基礎概念部分介紹Linux的某些機制,主要想表達Binder驅動的出現的原因,如果對Linux熟悉的可以直接跳過這部分,看第五點即可
一、程序隔離
出於安全考慮,一個程序不能操作另一個程序的資料,進而一個作業系統必須具備程序隔離這個特性。在Linux系統中,虛擬記憶體機制為每個程序分配了線性連續的記憶體空間,作業系統將這種虛擬記憶體空間對映到實體記憶體空間,每個程序有自己的虛擬記憶體空間,進而不能操作其他程序的記憶體空間,每個程序只能操作自己的虛擬記憶體空間,只有作業系統才有許可權操作實體記憶體空間。程序隔離保證了每個程序的記憶體安全,但是在大多數情形下,不同程序間的資料通訊是不可避免的,因此作業系統必須提供跨程序通訊機制
二、使用者空間和核心空間
- 使用者空間:表示程序執行在一個特定的操作模式中,沒有接觸實體記憶體或裝置的許可權
- 核心空間:表示獨立於普通的應用程式,可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權
三、系統呼叫/核心態/使用者態
抽象來看,作業系統中安全邊界的概念就像環路的概念一樣(前提是系統支援這種特性),一個環上持有一個特定的許可權組,Intel 支援四層環,但是 Linux 只使用了其中的兩環(0號環持有全部許可權,3號環持有最少許可權,1號和2號環未使用),系統程序執行在1號環,使用者程序執行在3號環,如果一個使用者程序需要其他高階許可權,其必須從3號環過渡成0號環,過渡需要通過一個安全引數檢查的閘道器,這種過渡被稱為系統呼叫,在執行過程中會產生一定數量的計算開銷。所以,使用者空間要訪問核心空間的唯一方式就是系統呼叫(System Call)
四、核心模組/驅動
通過系統呼叫,使用者空間可以訪問核心空間,它是怎麼做到訪問核心空間的呢?Linux的動態可載入核心模組機制解決了這個問題,模組是具有獨立功能的程式,它可以被單獨編譯,但不能獨立執行。這樣,Android系統可以通過新增一個核心模組執行在核心空間,使用者程序之間的通過這個模組作為橋樑,就可以完成通訊了。在Android系統中,這個執行在核心空間的,負責各個使用者程序通過Binder通訊的核心模組叫做Binder驅動
五、簡單的總結
將前面的所有概念連線起來理解就會非常好消化知識點:
- Linux的虛擬記憶體機制導致記憶體的隔離,進而導致程序隔離
- 程序隔離的出現導致對記憶體的操作被劃分為使用者空間和核心空間
- 使用者空間需要跨許可權去訪問核心空間,必須使用系統呼叫去實現
- 系統呼叫需要藉助核心模組/驅動去完成
前三步決定了程序間通訊需要藉助核心模組/驅動去實現,而Binder驅動就是核心模組/驅動中用來實現程序間通訊的
為什麼要用Binder
Linux提供有管道、訊息佇列、訊號量、記憶體共享、套接字等跨程序方式,那為什麼Android要選擇Binder另起爐灶呢?
一、傳輸效能好
- Socket:是一個通用介面,導致其傳輸效率低,開銷大
- 共享記憶體:雖然在傳輸時不需要拷貝資料,但其控制機制複雜
- Binder:複雜資料型別傳遞可以複用記憶體,需要拷貝1次資料
- 管道和訊息佇列:採用儲存轉發方式,至少需要拷貝2次資料,效率低
二、安全性高
- 傳統的程序:通訊方式對於通訊雙方的身份並沒有做出嚴格的驗證,只有在上層協議上進行架設
- Binder機制:從協議本身就支援對通訊雙方做身份校檢,因而大大提升了安全性
Binder通訊模型
首先在理解模型之前先熟悉這幾個概念:
- Client程序:跨程序通訊的客戶端(執行在某個程序)
- Server程序:跨程序通訊的服務端(執行在某個程序)
- Binder驅動:跨程序通訊的介質
- ServiceManager:跨程序通訊中提供服務的註冊和查詢(執行在System程序)
這裡只是個簡單的模型而已,只需理解模型的通訊流程:
- Server端通過Binder驅動在ServiceManager中註冊
- Client端通過Binder驅動獲取ServiceManager中註冊的Server端
- Client端通過Binder驅動和Server端進行通訊
Binder通訊原理
理解完模型流程之後,開始理解模型的通訊原理:
- Service端通過Binder驅動在ServiceManager的查詢表中註冊Object物件的add方法
- Client端通過Binder驅動在ServiceManager的查詢表中找到Object物件的add方法,並返回proxy物件的add方法,add方法是個空實現,proxy物件也不是真正的Object物件,是通過Binder驅動封裝好的代理類的add方法
- 當Client端呼叫add方法時,Client端會呼叫proxy物件的add方法,通過Binder驅動去請求ServiceManager來找到Service端真正物件,然後呼叫Service端的add方法
Binder物件和Binder驅動
- Binder物件:Binder機制中進行程序間通訊的物件,對於Service端為Binder本地物件,對於Client端為Binder代理物件
- Binder驅動:Binder機制中進行程序間通訊的介質,Binder驅動會對具有跨程序傳遞能力的物件做特殊處理,自動完成代理物件和本地物件的轉換
由於Binder驅動會對具有跨程序傳遞能力的物件做特殊處理,自動完成代理物件和本地物件的轉換,因此在驅動中儲存了每一個跨越程序的Binder物件的相關資訊,Binder本地物件(或Binder實體)儲存在binder_node的資料結構,Binder代理物件(或Binder引用/控制代碼)儲存在binder_ref的資料結構
Java層的Binder
- Binder類:是Binder本地物件
- BinderProxy類:是Binder類的內部類,它代表遠端程序的Binder物件的本地代理
- Parcel類:是一個容器,它主要用於儲存序列化資料,然後可以通過Binder在程序間傳遞這些資料
- IBinder介面:代表一種跨程序傳輸的能力,實現這個介面,就能將這個物件進行跨程序傳遞
- IInterface介面:client端與server端的呼叫契約,實現這個介面,就代表遠端server物件具有什麼能力,因為IInterface介面的asBinder方法的實現可以將Binder本地物件或代理物件進行返回
Binder類和BinderProxy類都繼承自IBinder,因而都具有跨程序傳輸的能力,在跨越程序的時候,Binder驅動會自動完成這兩個物件的轉換。IBinder是遠端物件的基本介面,是為高效能而設計的輕量級遠端呼叫機制的核心部分,但它不僅用於遠端呼叫,也用於程序內呼叫。IBinder介面定義了與遠端物件互動的協議,建議不要直接實現這個介面,而應該從Binder派生。Binder實現了IBinder介面,但是一般不需要直接實現此類,而是跟據你的需要由開發包中的工具生成,這個工具叫aidi。你通過aidi語言定義遠端物件的方法,然後用aidi工具生成Binder的派生類,然後使用它
AIDL
由於編譯工具會給我們生成一個Stub的靜態內部類,這個類繼承了Binder, 說明它是一個Binder本地物件,它實現了IInterface介面,表明它具有遠端Server承諾給Client的能力
一、服務端
在服務端中,我們只要實現Stub抽象類,和實現其方法即可
private IBinder myS = new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int add(int num1, int num2) throws RemoteException {
Log.i("Hensen", "從客戶端發來的AIDL請求:num1->" + num1 + "::num2->" + num2);
return num1 + num2;
}
};
二、客戶端
在客戶端中,可以通過bindService的回撥中獲取AIDL介面
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iMyAidlInterface = null;
}
};
public void add(View view) {
try {
int res = iMyAidlInterface.add(1, 2);
Log.i("Hensen", "從服務端呼叫成功的結果:" + res);
} catch (RemoteException e) {
e.printStackTrace();
}
}
梳理客戶端的呼叫流程:
- 呼叫Stub.asInterface獲取BinderProxy物件
- 呼叫BinderProxy物件的add方法
三、分析原理
1、Stub
Stub類繼承自Binder,意味著這個Stub其實自己是一個Binder本地物件,然後實現了IMyAidlInterface介面,IMyAidlInterface本身是一個IInterface,因此他攜帶某種客戶端需要的能力(這裡是方法add)。此類有一個內部類Proxy,也就是Binder代理物件
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\workspace5\\Boke\\app\\src\\main\\aidl\\com\\handsome\\boke\\IMyAidlInterface.aidl
*/
package com.handsome.boke;
// Declare any non-default types here with import statements
public interface IMyAidlInterface extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.handsome.boke.IMyAidlInterface {
private static final java.lang.String DESCRIPTOR = "com.handsome.boke.IMyAidlInterface";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.handsome.boke.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.handsome.boke.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.handsome.boke.IMyAidlInterface))) {
return ((com.handsome.boke.IMyAidlInterface) iin);
}
return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_basicTypes: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0 != data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.handsome.boke.IMyAidlInterface {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean) ? (1) : (0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public int add(int num1, int num2) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num1);
_data.writeInt(num2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
public int add(int num1, int num2) throws android.os.RemoteException;
}
2、asInterface
當客戶端bindService的onServiceConnecttion的回撥裡面,通過asInterface方法獲取遠端的service的。其函式的引數IBinder型別的obj,這個物件是驅動給我們的,如果是Binder本地物件,那麼它就是Binder型別,如果是Binder代理物件,那就是BinderProxy型別。asInterface方法中會呼叫queryLocalInterface,查詢Binder本地物件,如果找到,說明Client和Server都在同一個程序,這個引數直接就是本地物件,直接強制型別轉換然後返回,如果找不到,說明是遠端物件那麼就需要建立Binder代理物件,讓這個Binder代理物件實現對於遠端物件的訪問
/**
* Cast an IBinder object into an com.handsome.boke.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.handsome.boke.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.handsome.boke.IMyAidlInterface))) {
return ((com.handsome.boke.IMyAidlInterface) iin);
}
return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj);
}
3、add
當客戶端呼叫add方法時,首先用Parcel把資料序列化,然後呼叫mRemote.transact方法,mRemote就是new Stub.Proxy(obj)傳進來的,即BinderProxy物件
@Override
public int add(int num1, int num2) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num1);
_data.writeInt(num2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
4、transact
BinderProxy的transact方法是native方法,它的實現在native層,它會去借助Binder驅動完成資料的傳輸,當完成資料傳輸後,會喚醒Server端,呼叫了Server端本地物件的onTransact函式
public native boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
5、onTransact
在Server程序裡面,onTransact根據呼叫code會呼叫相關函式,接著將結果寫入reply並通過super.onTransact返回給驅動,驅動喚醒掛起的Client程序裡面的執行緒並將結果返回
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
6、題外話
為什麼生成的檔案不直接分為1個介面,2個類,清晰明瞭。Android這樣子設計是有道理的,當有多個AIDL檔案時候,Stub和Proxy類就會重名,把它們放在各自的AIDL介面中,就區分開來了
AMS的Binder體系
AMS是Android中最核心的服務,主要負責系統中四大元件的啟動、切換、排程及應用程序的管理和排程等工作,從圖中可以看出:
AMS | Binder角色 |
---|---|
IActivityManager | IInterface |
ActivityManagerNative | Binder本地物件 |
ActivityManagerProxy | Binder代理物件 |
ActivityManagerService | Service端 |
ActivityManager | 普通管理類 |
結語
這裡只是簡單的理解下Binder機制的基本原理,後續有時間會研究framework層的知識,如果有興趣的同學可以不依賴AIDL工具,手寫遠端Service完成跨程序通訊,這樣就可以加深對AIDL和Binder的理解,個人覺得這樣是最好的記憶方式,一起來寫吧