目錄
前言
什麼是Binder?
為什麼跨程序通信要使用binder?
Binder通信模型
從AIDL的角度來了解Binder
AIDL java代碼分析
demo:手寫最簡單的aidl
問題
文章參考:
寫給 Android 應用工程師的 Binder 原理剖析
Binder學習指南
前言
本篇文章主要圍繞下面的幾個點進行Binder學習的記錄。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLwUFVOhXWU5UMNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5gzMyIDN1ADMxATMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
什麼是Binder?
一句話概括:Binder是Android中跨程序通信的一種方式。
為什麼跨程序通信要使用binder?
參考:寫給 Android 應用工程師的 Binder 原理剖析
文章主要從性能、穩定性、安全性進行對比總結binder的優勢所在。
性能方面:穩定性:
- socket作為一款通用的接口,其傳輸效率低,開銷大。主要用在跨網絡的程序間通信和本機上程序間的低速通信。
- 消息隊列和管道采用存儲-轉發方式,即資料先從發送方緩存區拷貝到核心開辟的緩存區中,然後再從核心緩存區拷貝到接收方緩存區,至少有兩次拷貝過程。
- 共享記憶體雖然無需拷貝,但控制複雜,難以使用
- Binder 隻需要一次資料拷貝,性能上僅次于共享記憶體。
- Binder 基于 C/S 架構,用戶端(Client)有什麼需求就丢給服務端(Server)去完成,架構清晰、職責明确又互相獨立,自然穩定性更好。
- 共享記憶體雖然無需拷貝,但是在并發讀寫使它變得極為不可靠。
安全性
傳統的 IPC 接收方無法獲得對方可靠的程序使用者ID/程序ID(UID/PID),進而無法鑒别對方身份。Android 為每個安裝好的 APP 配置設定了自己的 UID,故而程序的 UID 是鑒别程序身份的重要标志。傳統的 IPC 隻能由使用者在資料包中填入 UID/PID,但這樣不可靠,容易被惡意程式利用。可靠的身份辨別隻有由 IPC 機制在核心中添加。其次傳統的 IPC 通路接入點是開放的,隻要知道這些接入點的程式都可以和對端建立連接配接,不管怎樣都無法阻止惡意程式通過猜測接收方位址獲得連接配接。同時 Binder 既支援實名 Binder,又支援匿名 Binder,安全性高。
Binder通信模型
摘自 Binder學習指南
對于跨程序通信的雙方,我們姑且叫做Server程序(簡稱Server),Client程序(簡稱Client);由于程序隔離的存在,它們之間沒辦法通過簡單的方式進行通信,那麼Binder機制是如何進行的呢?
回想一下日常生活中我們通信的過程:假設A和B要進行通信,通信的媒介是打電話(A是Client,B是Server);A要給B打電話,必須知道B的号碼,這個号碼怎麼擷取呢?通信錄.
這個通信錄就是一張表;内容大緻是:
1 2
B -> 12345676 C -> 12334354
先查閱通信錄,拿到B的号碼;才能進行通信;否則,怎麼知道應該撥什麼号碼?回想一下古老的電話機,如果A要給B打電話,必須先連接配接通話中心,說明給我接通B的電話;這時候通話中心幫他呼叫B;連接配接建立,就完成了通信。
另外,光有電話和通信錄是不可能完成通信的,沒有基站支援;資訊根本無法傳達。
我們看到,一次電話通信的過程除了通信的雙方還有兩個隐藏角色:通信錄和基站。Binder通信機制也是一樣:兩個運作在使用者空間的程序要完成通信,必須借助核心的幫助,這個運作在核心裡面的程式叫做Binder驅動,它的功能類似于基站;通信錄呢,就是一個叫做ServiceManager的東西(簡稱SM)
OK,Binder的通信模型就是這麼簡單,如下圖:
![]()
Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題
Binder通信原理
摘自 Binder學習指南
上文給出了Binder的通信模型,指出了通信過程的四個角色: Client, Server, SM, driver; 但是我們仍然不清楚Client到底是如何與Server完成通信的。
兩個運作在使用者空間的程序A和程序B如何完成通信呢?核心可以通路A和B的所有資料;是以,最簡單的方式是通過核心做中轉;假設程序A要給程序B發送資料,那麼就先把A的資料copy到核心空間,然後把核心空間對應的資料copy到B就完成了;使用者空間要操作核心空間,需要通過系統調用;剛好,這裡就有兩個系統調用:
,
copy_from_user
copy_to_user
。
但是,Binder機制并不是這麼幹的。講這麼一段,是說明程序間通信并不是什麼神秘的東西。那麼,Binder機制是如何實作跨程序通信的呢?
Binder驅動為我們做了一切。
假設Client程序想要調用Server程序的
對象的一個方法
object
;對于這個跨程序通信過程,我們來看看Binder機制是如何做的。 (通信是一個廣泛的概念,隻要一個程序能調用另外一個程序裡面某對象的方法,那麼具體要完成什麼通信内容就很容易了。)
add
![]()
Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題 Alt text
首先,Server程序要向SM注冊;告訴自己是誰,自己有什麼能力;在這個場景就是Server告訴SM,它叫
,它有一個
zhangsan
對象,可以執行
object
操作;于是SM建立了一張表:
add
zhangsan
這個名字對應程序Server;
然後Client向SM查詢:我需要聯系一個名字叫做
的程序裡面的
zhangsan
對象;這時候關鍵來了:程序之間通信的資料都會經過運作在核心空間裡面的驅動,驅動在資料流過的時候做了一點手腳,它并不會給Client程序傳回一個真正的
object
對象,而是傳回一個看起來跟
object
一模一樣的代理對象
object
,這個
objectProxy
也有一個
objectProxy
方法,但是這個
add
方法沒有Server程序裡面
add
對象的
object
方法那個能力;
add
的
objectProxy
add
隻是一個傀儡,它唯一做的事情就是把參數包裝然後交給驅動。(這裡我們簡化了SM的流程,見下文)
但是Client程序并不知道驅動傳回給它的對象動過手腳,畢竟僞裝的太像了,如假包換。Client開開心心地拿着
對象然後調用
objectProxy
方法;我們說過,這個
add
add
什麼也不做,直接把參數做一些包裝然後直接轉發給Binder驅動。
驅動收到這個消息,發現是這個
;一查表就明白了:我之前用
objectProxy
替換了
objectProxy
發送給Client了,它真正應該要通路的是
object
對象的
object
方法;于是Binder驅動通知Server程序,調用你的object對象的
add
方法,然後把結果發給我,Sever程序收到這個消息,照做之後将結果傳回驅動,驅動然後把結果傳回給
add
Client
程序;于是整個過程就完成了。
由于驅動傳回的
與Server程序裡面原始的
objectProxy
object
是如此相似,給人感覺好像是直接把Server程序裡面的對象object傳遞到了Client程序;是以,我們可以說Binder對象是可以進行跨程序傳遞的對象
但事實上我們知道,Binder跨程序傳輸并不是真的把一個對象傳輸到了另外一個程序;傳輸過程好像是Binder跨程序穿越的時候,它在一個程序留下了一個真身,在另外一個程序幻化出一個影子(這個影子可以很多個);Client程序的操作其實是對于影子的操作,影子利用Binder驅動最終讓真身完成操作。
了解這一點非常重要;務必仔細體會。另外,Android系統實作這種機制使用的是代理模式, 對于Binder的通路,如果是在同一個程序(不需要跨程序),那麼直接傳回原始的Binder實體;如果在不同程序,那麼就給他一個代理對象(影子);我們在系統源碼以及AIDL的生成代碼裡面可以看到很多這種實作。
另外我們為了簡化整個流程,隐藏了SM這一部分驅動進行的操作;實際上,由于SM與Server通常不在一個程序,Server程序向SM注冊的過程也是跨程序通信,驅動也會對這個過程進行暗箱操作:SM中存在的Server端的對象實際上也是代理對象,後面Client向SM查詢的時候,驅動會給Client傳回另外一個代理對象。Sever程序的本地對象僅有一個,其他程序所擁有的全部都是它的代理。
一句話總結就是:Client程序隻不過是持有了Server端的代理;代理對象協助驅動完成了跨程序通信。
從AIDL的角度來了解Binder
binder學習指南中舉了定義了一個簡單的aidl接口ICompute
// ICompute.aidl
package com.example.test.app;
interface ICompute {
int add(int a, int b);
}
經過編譯生成的java代碼類結構如下:
Binder/IBinder: 它代表了一種跨程序傳輸的能力;隻要實作了這個接口,就能将這個對象進行跨程序傳遞。也可以将binder了解成一種協定,類似http在 用戶端 發起http請求,服務端接收到請求封包,做對應的處理将結果傳回給用戶端。在跨程序通信的過程中binder就是網絡通信中的http
IInterface: IInterface代表的就是遠端server對象具有什麼能力,具體來說,就是aidl裡面的接口。同時指導如何擷取跨程序傳輸的binder。
這樣來看的話,Stub繼承Binder實作了IInterface接口,既可以進行跨程序傳輸,同時可以直接和遠端進行互動。
Proxy:實作了IInterface接口通過它可以擷取到跨程序傳輸的binder
AIDL java代碼分析
aidl生成的java代碼如下:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.txl.tool;
// Declare any non-default types here with import statements
public interface ICompute extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.txl.tool.ICompute {
private static final java.lang.String DESCRIPTOR = "com.example.txl.tool.ICompute";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface( this, DESCRIPTOR );
}
/**
* Cast an IBinder object into an com.example.txl.tool.ICompute interface,
* generating a proxy if needed.
*/
public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
return ((com.example.txl.tool.ICompute) iin);
}
return new com.example.txl.tool.ICompute.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 {
java.lang.String descriptor = DESCRIPTOR;
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;
}
default: {
return super.onTransact( code, data, reply, flags );
}
}
}
private static class Proxy implements com.example.txl.tool.ICompute {
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;
}
@Override
public int add(int a, int b) 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( a );
_data.writeInt( b );
mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}
這裡的java代碼符合前面對Binder和IInterface的描述。
Server端實作的時候繼承Stub類,它能夠進行跨程序傳輸供Client端調用。
Client端調用Stub的靜态 方法asInterface擷取一個具有服務端外觀(和服務端有同樣的方法)的ICompute對象。
其實asInterface方法和Stub方法沒有什麼太大的關系。它的代碼如下:
public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
return ((com.example.txl.tool.ICompute) iin);
}
return new com.example.txl.tool.ICompute.Stub.Proxy( obj );
}
邏輯是:先從本地去檢視是否有符合需求的binder對象,如果有直接傳回本地對象。如果沒有生成一個Proxy對象傳回。
對于用戶端而言這個Proxy類與其說是一個代理對象倒不如說是它在指導Binder如何進行資料處理。我們看看它的add方法
@Override
public int add(int a, int b) 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( a );
_data.writeInt( b );
//除了這句代碼,其他的全部在進行資料處理
mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到當調用add方法的時候除了 mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );其他的都在進行資料處理。而transact就像是将指定資料同步發送到服務端然後等待響應。這也是前面我為什麼将Binder類比成http。
用戶端 transact方法接收4個參數,他們分别是
- 要調用方法的編碼
- 要發送給服務端參數的容器
- 要傳回資料的容器
- 标記falg(暫時不知道這個參數的作用)
服務端onTransact同樣的接收4個參數,他們分别與用戶端的4個參數對應。
這樣一次aidl跨程序通信就分析完了。做一個簡單的總結:
服務端:實作繼承Binder并實作IIterface,重寫onTransact方法處理用戶端的回調。
用戶端:同過綁定服務時得到的Ibinder對象,通過transact進行對應的資料傳輸。
demo:手寫最簡單的aidl
服務端實作:
package com.txl.calculationserver;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;
public class CalculationService extends Service {
private static final String TAG = "CalculationService";
Binder binder = new CalculationBinder();
public CalculationService() {
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.d( TAG,"onCreate" );
}
private static class CalculationBinder extends Binder implements IInterface {
private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;
public CalculationBinder() {
this.attachInterface( this, DESCRIPTOR );
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
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;
}
default: {
return super.onTransact( code, data, reply, flags );
}
}
}
public int add(int a, int b) throws RemoteException {
Log.d( TAG,"call add method by client" );
return a+b;
}
}
}
用戶端實作:
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private ProxyCalculationServer proxyCalculationServer;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
proxyCalculationServer = ProxyCalculationServer.asInterface( iBinder );
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
findViewById( R.id.tv_call_remote_add ).setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View view) {
if(proxyCalculationServer != null){
try {
int result = proxyCalculationServer.add( 3,6 );
Log.d( TAG,"result = "+result );
} catch (RemoteException e) {
e.printStackTrace();
}
}else {
Log.e( TAG,"proxyCalculationServer is null" );
}
}
} );
Intent intent = new Intent( );
intent.setAction( "com.txl.calculationserver" );
intent.setPackage( "com.txl.calculationserver" );
bindService( intent,connection,BIND_AUTO_CREATE );
}
static class ProxyCalculationServer implements IInterface {
private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;
private android.os.IBinder mRemote;
public ProxyCalculationServer(IBinder obj) {
mRemote = obj;
}
// @Override
// public android.os.IBinder asBinder() {
// return mRemote;
// }
public static ProxyCalculationServer asInterface(IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
if (((iin != null) && (iin instanceof ProxyCalculationServer))) {
return ((ProxyCalculationServer) iin);
}
return new ProxyCalculationServer( obj );
}
public int add(int a, int b) 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( a );
_data.writeInt( b );
mRemote.transact( TRANSACTION_add, _data, _reply, 0 );
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
}
這個兩個檔案分别實作在不同的項目中。對于簡單的xml沒有貼上代碼。
最後服務端和用戶端的運作結果如下
通過上面的案例可以看到,對于服務端而言不是一定要擷取一個Proxy對象。隻要你懂得binder傳輸過程中的資料處理,甚至完全可以直接自己調用transact。
總結:
aidl實作跨程序通信不是必須要先編寫 .aidl 檔案完全可以按照前面總結aidl需要完成的東西來進行編寫。但是aidl檔案幫助我們更加簡單的實作款程序通信的過程,也不容易出錯。建議大家在實作aidl的時候不要手寫,手寫實作過程的目的是為了更好的了解這一過程。
問題
- 服務端什麼時候被注冊到ServerManager?
- IInterface接口的asBinder方法是幹什麼的?為什麼傳回null也能夠正常調用?