1. 程式人生 > >Android中Binder學習

Android中Binder學習

關於Binder,我們需要知道,為什麼需要跨程序通訊(IPC),怎麼做到跨程序通訊?為什麼是Binder?

由於Android是基於Linux核心的,因此有些知識需要我們先了解:

程序隔離

程序隔離是為保護作業系統中程序互不幹猶而設計的一組不同硬體和軟體的技術。這個技術是為了避免程序A寫入程序B的情況發生。程序的隔離實現,使用了虛擬地址空間。程序A的虛擬地址和程序B的虛擬地址不同,這樣就放置程序A將資料資訊寫入程序B。

ps:虛擬地址就是邏輯地址。

作業系統的不同程序之間,資料不共享,對於每個程序來說,它都以為自己獨享了整個系統,完全不知道其他程序的存在,因此一個程序需要個另一個程序通訊,需要某種系統機制才能完成。

使用者空間/核心空間

Linux Kernel 是作業系統的核心,獨立於普通的應用程式,可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權。

對於Kernel這麼一個高安全級別的東西,顯然是不容許其他的應用隨便呼叫或訪問的,所以需要對Kernel提供一定的保護機制,這個保護機制用來告訴那些應用程式,只可以訪問某些許可的資源,不許可的資源是拒絕訪問的,也是就把Kernel和上層應用程式抽象的隔離開,分別稱為核心空間和使用者控制元件。

系統呼叫/核心態/使用者態

雖然從邏輯上抽離出使用者空間和核心空間,但是不可避免的是,有一些使用者空間需要訪問核心的資源,比如應用程式訪問檔案,網路等,怎麼辦呢?
使用者空間訪問核心空間的唯一方式就是系統呼叫;通過這個統一入口介面,所有的資源訪問都是在核心的控制下執行,以免導致使用者程式對系統資源的越權訪問,從而保障了系統的安全和穩定。

當一個任務(程序)執行系統呼叫而進入核心程式碼中執行時,我們就稱程序處於核心執行行態(或簡稱為核心態),此時處理器處於特權級最高的核心程式碼中執行。當程序在執行使用者自己的程式碼時,就稱其處於使用者執行狀態,即此時處理器在特權級最低的使用者程式碼中執行。
處理器在特權等級最高的時候才能執行那些特權CPU指令。

核心模組/驅動

通過系統呼叫,使用者控制元件可以訪問核心空間,那麼如果一個使用者空間想與另一個使用者空間進行通訊,怎麼辦呢?那麼肯定是通過作業系統核心了,通過新增作業系統核心的支援;傳統的Linux通訊機制,比如Socket,管道等都是核心支援的。
Binder並不是Linux核心的一部分,那麼它是怎麼做到訪問核心空間的呢?Linux的動態可載入核心模組機制解決了這個問題;
模組是具有獨立功能的程式,它可以被單獨被編譯,但是不能獨立執行。它在執行時被連線到核心作為核心的喲部分在核心空間執行。這樣,Android系統可以通過天際一個核心模組執行在核心空間,使用者程序之間的通訊就通過這個模組作為橋樑。

在Android系統中,這個執行在核心空間,負責各個使用者程序通過Binder通訊的核心模組叫做BInder驅動。

為什麼使用Binder?

Android使用的Linux核心擁有著非常多的跨程序通訊機制,比如管道,System V,Socket等;為什麼還需要單獨搞一個Binder出來呢?主要有兩點,效能和安全。在移動裝置上,廣泛地使用跨程序通訊肯定對通訊機制本身提出了嚴格的要求;Binder相對出傳統的Socket方式,更加高效;另外,傳統的程序通訊方式對於通訊雙方的身份並沒有做出嚴格的驗證,只有在上層協議上進行架設;比如Socket通訊ip地址是客戶端手動填入的,都可以進行偽造;而Binder機制從協議本身就支援對通訊雙方做身份校檢,因而大大提升了安全性。這個也是Android許可權模型的基礎。

Binder相較於傳統IPC來說更適合於Android系統,具體原因的包括如下三點:

  1. Binder本身是C/S架構的,這一點更符合Android系統的架構
  2. 效能上更有優勢:管道,訊息佇列,Socket的通訊都需要兩次資料拷貝,而Binder只需要一次。要知道,對於系統底層的IPC形式,少一次資料拷貝,對整體效能的影響是非常之大的
  3. 安全性更好:傳統IPC形式,無法得到對方的身份標識(UID/GID),而在使用Binder IPC時,這些身份標示是跟隨呼叫過程而自動傳遞的。Server端很容易就可以知道Client端的身份,非常便於做安全檢查
Binder的架構

在這裡插入圖片描述

  • Binder通訊採用C/S架構,從組建視角來講,包含Client,Server,ServiceManager以及Binder驅動,其中ServiceManager用於管理系統中各種Service。
  • Binder在framwork層進行了封裝,通過JNI技術呼叫Native層的Binder架構。
  • Binder在Native層以ioctl的方式與Binder驅動通訊。
Binder的通訊模型

兩個執行在使用者空間的程序要完成通訊,必須藉助核心的幫助,這個執行在核心裡面的程式叫做Binder驅動,它的功能類似於基站;ServiceManager(簡稱SM)的功能類似於通訊錄。

在這裡插入圖片描述

通訊的步驟:

  1. ServiceManager建立:首先有一個程序向驅動提出申請SM,驅動同意後,SM程序負責管理Service
  2. 各個Service向SM註冊:每個Server端程序啟動後,向SM報告,我是XXX,如果要找我就返回0x1234(類比)。其他Server程序也是如此;這樣SM就建立了一張表,對應著各個Server的名字和地址。
  3. Client想要與Server通訊,首先詢問SM,請告訴我如何聯絡zhangsan,SM收到後給他一個號碼0x1234;Client收到之後,開心滴用這個號碼撥通了Server的電話,於是就開始通訊了。

那麼Binder驅動幹什麼去了呢?這裡Client與SM的通訊,以及Client與Server的通訊,都會經過驅動,驅動在背後默默無聞,但是做著最重要的工作。

Binder機制跨程序原理

由上面的知識,我們可以知道,兩個程序想要通訊,肯定要通過核心進行中轉,要知道,核心是可以訪問所有程序的資料的,我們直接可以想到,加入程序A,B,程序A要給程序B傳送訊息,那麼可以將A的資料複製一份到核心,核心再將對應的資料複製到B。

但是Binder不是這樣做的。Binder驅動為我們做了一切。

假設Client程序想要呼叫Server程序的Object物件的一個add方法,
首先,Server程序向SM註冊:告訴自己是誰,有什麼能力,正這個場景中,Server告訴SM,它叫zhangsan,它有一個object物件,可以執行add操作;於是SM就建立了一張表,zhangsan這個名字就對應了這個Server。

然後Client向SM查詢:我需要聯絡一個名字叫zhangsan的程序裡面的Object物件;這個時候,核心裡面的驅動不會給Client程序返回一個真正的Object物件,而是返回一個看起來跟Object一模一樣的代理物件objectProxy,這個objectProxy也有有個add方法,但是這個add方法沒有Servier程序裡面Object物件中的add方法那個能力;objectProxy的add只是一個傀儡,它唯一做的事情就是把引數包裝起來轉發給Binder驅動。

驅動收到這個訊息,發現是這個objectProxy;一查表就明白了:我之前用objectProxy替換了object傳送給Client了,它真正應該要訪問的是object物件的add方法;於是Binder驅動通知Server程序,呼叫你的object物件的add方法,然後把結果發給我,Sever程序收到這個訊息,照做之後將結果返回驅動,驅動然後把結果返回給Client程序;於是整個過程就完成了。

通過這個,我們可以知道,Binder跨程序傳輸並不是真的把一個物件傳輸到了另外一個程序;傳輸過程好像是Binder跨程序穿越的時候,它在一個程序留下了一個真身,在另外一個程序幻化出一個影子(這個影子可以很多個);Client程序的操作其實是對於影子的操作,影子利用Binder驅動最終讓真身完成操作。

對於Binder的訪問,如果是在同一個程序中,那麼直接返回原始的Binder實體,如果是在不同程序,那麼就給它一個代理影子;

另外我們為了簡化整個流程,隱藏了SM這一部分驅動進行的操作;實際上,由於SM與Server通常不在一個程序,Server程序向SM註冊的過程也是跨程序通訊,驅動也會對這個過程進行暗箱操作:SM中存在的Server端的物件實際上也是代理物件,後面Client向SM查詢的時候,驅動會給Client返回另外一個代理物件。Sever程序的本地物件僅有一個,其他程序所擁有的全部都是它的代理。

一句話總結就是:Client程序只不過是持有了Server端的代理;代理物件協助驅動完成了跨程序通訊。

BInder的面對物件

Binder使用Client-Server通訊方式:一個程序作為Server提供如視訊/音訊解碼,視訊捕獲,地址本查詢,網路連線等服務;多個程序作為Client向Server發起服務請求,獲得所需要的服務。對Binder而言,Binder可以看成Server提供的實現某個特定服務的訪問接入點,Client通過這個‘地址’向Server傳送請求來使用該服務;對Client而言,Binder可以看成是通向Server的入口,要想和某個Server通訊,必須先建立這個入口並獲取這個入口。

與其他IPC不同,Binder使用了面對物件的思想來描述訪問接入點的Binder及其在Client中的入口:Binder是一個實體位於Server中的物件,該物件提供了一套用以實現對服務的請求,就像類的成員函式。在Client看來,通過Binder‘指標’呼叫其提供的方法和通過指標呼叫其他任何本地物件的方法並沒有什麼區別,儘管前者的實體位於遠端的Server中,而後者的實體位於本地記憶體中。從通訊的角度來看,Client中的Binder也可以看做是Server Binder的‘代理’,在本地代表遠端Server為Client提供服務。

面向物件思想的引入將程序間通訊轉化為通過對某個Binder物件的引用呼叫該物件的方法,而其獨特之處在於Binder物件是一個可以跨程序引用的物件,它的實體位於一個程序中,而它的引用卻遍佈於系統的各個程序之中。最誘人的是,這個引用和java裡引用一樣既可以是強型別,也可以是弱型別,而且可以從一個程序傳給其它程序,讓大家都能訪問同一Server,就象將一個物件或引用賦值給另一個引用一樣。Binder模糊了程序邊界,淡化了程序間通訊過程,整個系統彷彿運行於同一個面向物件的程式之中。形形色色的Binder物件以及星羅棋佈的引用彷彿粘接各個應用程式的膠水,這也是Binder在英文裡的原意。

理解Java層的Binder
IBinder/IInterface/Binder/BinderProxy/Stub

我們在使用AIDL介面的時候,經常會接觸到這些類,那麼每個類代表的是什麼呢?

  • IBinder:是一個介面,它代表了一種跨程序傳輸的能力;只要實現了這個介面,就能將這個物件進行跨程序傳遞,這是驅動底層支援的;在跨程序資料流經驅動的時候,驅動會失敗IBinder型別的資料,從而自動完成不同程序Binder本地物件以及Binder代理物件的轉換。
  • IBinder負責資料傳輸,那麼client與server端的呼叫契約(這裡不用介面避免混淆)呢?這裡的IInterface代表的就是遠端server物件具有什麼能力。具體來說,就是aidl裡面的介面。
  • Java層的Binder類,代表的其實就是Binder本地物件。BinderProxy類是Binder類的一個內部類,它代表遠端程序的Binder物件的本地代理;這兩個類都繼承自IBinder,因為都具有跨程序傳輸的能力;實際上,在跨越程序的時候,Binder驅動會自動完成這兩個物件的轉換。
  • 在使用AIDL的時候,編譯工具會給我們生成一個Stub的靜態內部類;這個類繼承了Binder, 說明它是一個Binder本地物件,它實現了IInterface介面,表明它具有遠端Server承諾給Client的能力;Stub是一個抽象類,具體的IInterface的相關實現需要我們手動完成,這裡使用了策略模式。
AIDL過程分析

首先定義一個簡單的AIDL介面:

interface ICalculate {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
   int add(int first,int second);
   int sub(int first,int second);
}

經過編譯後,會在app/build/generated/source/aidl下生成一個ICalculate.java的Interface

public interface ICalculate extends android.os.IInterface{
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements 
	com.example.asus1.remoteservice.ICalculate{
		private static final java.lang.String DESCRIPTOR = 	
					"com.example.asus1.remoteservice.ICalculate";
		/** Construct the stub at attach it to the interface. */
		public Stub(){
		this.attachInterface(this, DESCRIPTOR);
		}
		/**
 		* Cast an IBinder object into an com.example.asus1.remoteservice.ICalculate interface,
 		* generating a proxy if needed.
 		*/
		public static com.example.asus1.remoteservice.ICalculate asInterface(android.os.IBinder obj){
		if ((obj==null)) {
			return null;
		}
		android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
		if (((iin!=null)&&(iin instanceof com.example.asus1.remoteservice.ICalculate))) {
			return ((com.example.asus1.remoteservice.ICalculate)iin);
		}
		return new com.example.asus1.remoteservice.ICalculate.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_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;
		}
			case TRANSACTION_sub:
		{
			data.enforceInterface(DESCRIPTOR);
			int _arg0;
			_arg0 = data.readInt();
			int _arg1;
			_arg1 = data.readInt();
			int _result = this.sub(_arg0, _arg1);
			reply.writeNoException();
			reply.writeInt(_result);
			return true;
		}
	}
		return super.onTransact(code, data, reply, flags);
	}
private static class Proxy implements com.example.asus1.remoteservice.ICalculate
{
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 first, int second) 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(first);
_data.writeInt(second);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public int sub(int first, int second) 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(first);
_data.writeInt(second);
mRemote.transact(Stub.TRANSACTION_sub, _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);
static final int TRANSACTION_sub = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public int add(int first, int second) throws android.os.RemoteException;
public int sub(int first, int second) throws android.os.RemoteException;
}

系統會幫我們生成這個檔案,之後我們只需要繼承ICalculate.Stub這個抽象類,實現它的方法,然後在Service的onBinder方法裡面返回就實現了AIDL。

Stub類繼承自Binder,以為著這個Stub其實自己就是一個Binder本地物件,然後實現了ICalculate介面,ICalculate本身就是一個IInterface,因此它攜帶某種客戶端需要的功能(這裡是add方法和sub方法)。此類裡面還有一個內部類Proxy,也就是Binder代理物件。

在Stub類的構造方法中,我們可以看到:


private static final java.lang.String DESCRIPTOR = "com.example.asus1.remoteservice.ICalculate";
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}

    public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    

然後看看asInterface方法,我們在bind一個Service之後,onServiceConnection的回撥裡面,就是通過這個方法拿到一個遠端的service的IBinder(遠端的,就是BinderProxy),所以這個方法做了什麼呢?

public static com.example.asus1.remoteservice.ICalculate asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
//看本地有沒有
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.asus1.remoteservice.ICalculate))) {
return ((com.example.asus1.remoteservice.ICalculate)iin);
}
return new com.example.asus1.remoteservice.ICalculate.Stub.Proxy(obj);
}


  public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

首先看方法的引數IBinder型別的obj,這個物件是驅動給我們的,如果是Binder本地物件,那麼它就是Binder型別,如果是Binder代理物件,那就是BinderProxy型別;然後,它會試著查詢Binder本地物件,如果找到,說明Client和Server都在同一個程序中,這個引數直接就是本地物件,也就是Stub,直接強制型別轉換然後返回。如果找不到,說明是遠端物件(處於另一個程序),那麼就需要建立一個Binder代理物件,讓這個Binder代理實現對於遠端物件的訪問。一般來說,如果是與一個遠端Servie物件進行通訊,那麼這裡返回的一定是一個Binder代理物件,這個IBinder引數實際上是BinderProxy;

Proxy(android.os.IBinder remote)
{
mRemote = remote;
}

然後我們在看看我們AIDL中的add方法的實現:在Stub類裡面,add是一個抽象方法,我們需要繼承這個類並實現它;如果Client和Server在同一個程序,那麼直接就是呼叫這個方法;
如果是遠端呼叫,這中間發生了什麼呢?Client是如何呼叫到到Server的方法的呢?

根據上面分析,我們知道,對於遠端方法的呼叫,肯定是通過Binder代理完成的,在這個例子中,就是Proxy類:

private static class Proxy implements com.example.asus1.remoteservice.ICalculate
{
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 first, int second) 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(first);
_data.writeInt(second);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public int sub(int first, int second) 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(first);
_data.writeInt(second);
mRemote.transact(Stub.TRANSACTION_sub, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}

它首先用parcel把資料序列化,然後呼叫了transact方法,這個transact方法是幹嘛的呢?這個Proxy類在asInterface方法裡面被建立,前面提到過,如果是Binder代理那麼說明驅動返回的IBinder實際是BinderProxy, 因此我們的Proxy類裡面的mRemote實際型別應該是BinderProxy;我們看看BinderProxy的transact方法:(Binder.java的內部類)

 public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");

        if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
            // For now, avoid spamming the log by disabling after we've logged
            // about this interface at least once
            mWarnOnBlocking = false;
            Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
                    new Throwable());
        }

        final boolean tracingEnabled = Binder.isTracingEnabled();
        if (tracingEnabled) {
            final Throwable tr = new Throwable();
            Binder.getTransactionTracker().addTrace(tr);
            StackTraceElement stackTraceElement = tr.getStackTrace()[1];
            Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
                    stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
        }
        try {
            return transactNative(code, data, reply, flags);
        } finally {
            if (tracingEnabled) {
                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
            }
        }
    }


 public native boolean transactNative(int code, Parcel data, Parcel reply,