韌館-LearnHouse

簡易理解Android Binder

由於一直搞不懂Android Binder,只知道是一個IPC機制。網路上看了很多篇,但到最後都似懂非懂,然後就忘了到底在講啥。
一直到看到對岸中國的一篇文章,覺得簡單明瞭,為了預防之後連結失效,在這裡備份一下
原文網址:https://kknews.cc/code/z2vgzl.html
如果想要徹底了解呢,則可以看看這篇:https://zhuanlan.zhihu.com/p/35519585

本文分為以下幾個部分去介紹

  • Android 整體架構
  • Binder IPC 的架構
  • 手動實現 Binder IPC
  • 使用 AIDL 實現 Binder IPC

 

如果覺得文章太長,可以先只看「小結」部分,小結會把每個部分的重點總結出來,有些部分可以跳過。

一,Android 整體架構

不識廬山真面目,只緣身在此山中,所以我們先來大概看下 Android 這座大山的整體輪廓。我們先從 Android 的整體架構來看看 Binder 是處於什麼地位,這張圖引自 Android 項目開源網站:https://source.android.com

從下往上依次為

  • 內核層:Linux 內核和各類硬體設備的驅動,這裡需要注意的是,Binder IPC 驅動也是在這一層實現,比較特殊
  • 硬體抽象層:封裝「內核層」硬體驅動,提供可供「系統服務層」調用的統一硬體接口
  • 系統服務層:提供核心服務,並且提供可供「應用程式框架層」調用的接口
  • Binder IPC 層:作為「系統服務層」與「應用程式框架層」的 IPC 橋樑,互相傳遞接口調用的數據,實現跨進層的通訊
  • 應用程式框架層:這一層可以理解為 Android SDK,提供四大組件,View 繪製體系等平時開發中用到的基礎部件

在一個大的項目裡面,分層是非常重要的,處於最底層的接口最具有「通用性」,接口粒度最細,越往上層通用性降低。理論上來說上面的每一層都可以「開放」給開發者調用,例如開發者可以直接調用硬體抽象層的接口去操作硬體,或者直接調用系統服務層中的接口去直接作業系統服務,甚至是像 Windows 開發一樣,開發者可以在內核層寫程序,運行在內核中。不過開放帶來的問題就是開發者權利太大,對於系統的穩定性是沒有任何好處的,一個病毒製作者寫了一個內核層的病毒,系統也許永遠也起不來了。所以谷歌的做法是將開發者的權利收攏到了「應用程式框架層」,開發者只能調用這一層提供的接口。

上面的層次中,內核層與硬體抽象層均用 C/C++ 實現,系統服務層是以 Java 實現,硬體抽象層編譯為 so 文件,以 JNI 的形式供系統服務層使用。系統服務層中的服務隨系統的啟動而啟動,只要不關機,就會一直運行。這些服務幹什麼事情呢?其實很簡單,就是完成一個手機該有的核心功能如簡訊的收發管理、電話的接聽、掛斷以及應用程式的包管理、Activity 的管理等等。每一個服務均運行在一個獨立進程中,因為是以 Java 實現,所以本質上來說就是運行在一個獨立進程的 Dalvik 虛擬機中。問題就來了,開發者的 APP 運行在一個新的進程空間,如何調用到系統服務層中的接口呢?答案是 IPC(Inter-Process Communication),進程間通訊,縮寫與 RPC(Remote Procedure Call)是不一樣的,實現原理也是不一樣的。每一個系統服務在應用層序框架層都有一個 Manager 與之對應,方便開發者調用其相關的功能,具體關係大致如下

IPC 的方式有很多種,例如 socket、共享內存、管道、消息隊列等等,我們就不去深究為何要使用 Binder 而不使用其他方式去做,到目前為止,這座大山的面目算是有個大概的輪廓了。

1.1 小結

  • Android 從下而上分了內核層、硬體抽象層、系統服務層、Binder IPC 層、應用程式框架層
  • Android 中「應用程式框架層」以 SDK 的形式開放給開發者使用,「系統服務層」中的核心服務隨系統啟動而運行,通過應用層序框架層提供的 Manager 實時為應用程式提供服務調用。系統服務層中每一個服務運行在自己獨立的進程空間中,應用程式框架層中的 Manager 通過 Binder IPC 的方式調用系統服務層中的服務。

二,Binder IPC 的架構

下面我們就來看看 Binder IPC 的架構是怎樣的

Binder IPC 屬於 C/S 結構,Client 部分是用戶代碼,用戶代碼最終會調用 Binder Driver 的 transact 接口,Binder Driver 會調用 Server,這裡的 Server 與 service 不同,可以理解為 Service 中 onBind 返回的 Binder 對象,請注意區分下。

  • Client:用戶需要實現的代碼,如 AIDL 自動生成的接口類
  • Binder Driver:在內核層實現的 Driver
  • Server:這個 Server 就是 Service 中 onBind 返回的 IBinder 對象

需要注意的是,上面綠色的色塊部分都是屬於用戶需要實現的部分,而藍色部分是系統去實現了。也就是說 Binder Driver 這塊並不需要知道,Server 中會開啟一個線程池去處理客戶端調用。為什麼要用線程池而不是一個單線程隊列呢?試想一下,如果用單線程隊列,則會有任務積壓,多個客戶端同時調用一個服務的時候就會有來不及響應的情況發生,這是絕對不允許的。

對於調用 Binder Driver 中的 transact 接口,客戶端可以手動調用,也可以通過 AIDL 的方式生成的代理類來調用,服務端可以繼承 Binder 對象,也可以繼承 AIDL 生成的接口類的 Stub 對象。這些細節下面繼續接著說,這裡暫時不展開。

切記,這裡 Server 的實現是線程池的方式,而不是單線程隊列的方式,區別在於,單線程隊列的話,Server 的代碼是線程安全的,線程池的話,Server 的代碼則不是線程安全的,需要開發者自己做好多線程同步。

2.1 小結

  • Binder IPC 屬於 C/S 架構,包括 Client、Driver、Server 三個部分
  • Client 可以手動調用 Driver 的 transact 接口,也可以通過 AIDL 生成的 Proxy 調用
  • Server 中會啟動一個「線程池」來處理 Client 的調用請求,處理完成後將結果返回給 Driver,Driver 再返回給 Client

這裡就回答了開篇提問的兩個問題:Service 中通過 AIDL 提供的接口並不是線程安全的,同理 ContentProvider 底層也是使用 Binder,同樣不是線程安全的,至於是否需要做多線程保護,看業務而定,最好是做好多線程同步,以防萬一。

三,手動實現 Binder IPC

通過上面的講解,大家應該對整體的流程已經有了清楚的認識,下面我們先來看看如何手動實現 Binder IPC,即不使用 AIDL 的方式。對應上面的 Client、Driver、Server,在 Activity、Service 中分別是什麼呢?

上文說的 Server 其實就是 Service 中 onBind 返回的 IBinder 對象。

3.1 Server

假如我們要做一個上報數據的功能,運行在 Service 中,在後台上報數據,接口定義如下

public interface IReporter {
   int report(String values, int type);
}

那如何拿到它的 Server 對象呢?答案是通過 Service 的 onBind 方法返回,實現如下

BinderService.java

public class BinderService extends Service {
    public static final int REPORT_CODE = 0;

    public interface IReporter {
        int report(String values, int type);
    }

    public final class Reporter extends Binder implements IReporter {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            
            return super.onTransact(code, data, reply, flags);
        }

        @Override
        public int report(String values, int type) {
            return type;
        }
    }

    private Reporter mReporter;

    public BinderService() {
        mReporter = new Reporter();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mReporter;
    }
}

這裡我暫時不寫 onTransact 的實現部分,最主要的是繼承 Binder 對象,這個是 Android SDK 提供的基類,它實現了 IBinder 接口,並且封裝了底層的 Binder Driver,看看它是如何初始化的

Binder.java

public Binder() {
  init();
  ...
}
...
private native final void init();

這裡調用 native 的一個 init 方法,我們就不去深究了,知道它是對底層的 Binder Driver 的封裝即可。當客戶端發起請求的時候,Binder Driver 會調用它的 execTransact 方法,並在內部調用到 onTransact 方法,用戶端代碼可以重載該方法去實現自己的業務邏輯代碼。我們的實現方式如下

BinderService.java

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code) {
        case REPORT_CODE:
            data.enforceInterface("reporter");
            String values = data.readString();
            Log.i("IReporter", "data is '" + values + "'");
            int type = data.readInt();
            int result = report(values, type);
            reply.writeInterfaceToken("reporter");
            reply.writeInt(result);
            return true;
    }
    return super.onTransact(code, data, reply, flags);
}

這裡的主要過程就是:獲取到 data 中傳遞過來的參數 values 和 type,調用自己實現的 report 函數,將返回值寫到 reply 中。

注意,這裡實現的兩個關鍵點就是

  • Reporter 類繼承 Binder 類,重載 onTransact 函數,實現自己的業務邏輯
  • 在 Service 的 onBind 中返回 Reporter 類的實例

這裡看不到半點線程池的影子對吧,其實是在 Binder 內部的 native 方法中去實現了的,記住你寫的代碼要保持線程安全就對了。

3.2 Driver

該部分已經被 Binder 類給封裝了,暴露給開發者的已經是很簡單的使用方式了,即繼承 Binder,實現 onTransact 即可。

3.3 Client

那 Client 是什麼呢?也就是我們想使用 IReport 接口來做數據上報的地方,一般都在 Activity 裡面,主要實現如下

MainActivity.java

private IBinder mReporterBind;
private class BindConnection implements ServiceConnection {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mReporterBind = service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mReporterBind = null;
    }
}

...

@Override
protected void onCreate(Bundle savedInstanceState) {
    Intent intent = new Intent(this, BinderService.class);
    bindService(intent, new BindConnection(), BIND_AUTO_CREATE);
}

這樣就拿到了後台 Service 中的 onBind 返回的 IBinder 對象,也就是上面 Binder IPC 架構中的 mRemote 對象。至於 bindService 中做了什麼,有興趣的讀者可以再去研究,這裡知道它返回的就是 mRemote 對象即可。

MainActivity.java

Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("reporter");
data.writeString("this is a test string.");
data.writeInt(3);
try {
    mReporterBind.transact(BinderService.REPORT_CODE, data, reply, 0);
} catch (RemoteException e) {
    e.printStackTrace();
}
reply.enforceInterface("reporter");
int result = reply.readInt();
data.recycle();
reply.recycle();

通過 Parcel.obtain() 獲取發送包對象、應答包對象,寫入數據,調用 IBinder 的 transact 接口,即 mRemote.transact() 的調用。

3.4 小結

  • 一切複雜的邏輯均已經被封裝在實現了 IBinder 接口的 Binder 類中
  • Activity 中通過 bindService 拿到 Binder Driver 中的 mRemote 對象(IBinder 的實例),然後「組包」,然後「調用 transact 接口」按序發送數據包
  • Service 中繼承 Binder 類,「重載 onTransact 函數」,實現參數的「解包」,發送返回包等,在 onBind 中返回具體的實現類 如上文中的 Reporter

總的說來就是 Client 組包,調用 transact 發送數據,Server 接到調用,解包,返回,下面使用 AIDL 的流程本質也是一樣。

使用 AIDL 實現 Binder IPC

上面的例子我們看到,定義了 IReporter 接口,但是其實 Client 中並沒有用到,因為數據的組包和解包其實是手動編碼的,並不能直接調用接口,所以其實定義接口的意義等於 0,基於此,Android 給了我們更好用的方式那就是 AIDL,定義如下

IReporter.aidl

package com.android.binder;

interface IReporter {
	int report(String values, int type);
}

4.1 Server

AidlService.java

public class AidlService extends Service {

	public static final class Reporter extends IReporter.Stub {
		@Override
		public int report(String values, int type) throws RemoteException {
			return type;
		}
	}
	
	private Reporter mReporter;
	
	public AidlService() {
		mReporter = new Reporter();
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		return mReporter;
	}
}

這裡與手動實現的方式不同的是,一個繼承了 Binder,一個繼承了 AIDL 自動生成的 Stub 對象,它是什麼呢?我們可以看下它的定義

IReporter.java

public interface IReporter extends android.os.IInterface
{
	public static abstract class Stub extends android.os.Binder implements com.android.binder.IReporter {
	
	...
	
		@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_report:
				{
					data.enforceInterface(DESCRIPTOR);
					java.lang.String _arg0;
					_arg0 = data.readString();
					int _arg1;
					_arg1 = data.readInt();
					int _result = this.report(_arg0, _arg1);
					reply.writeNoException();
					reply.writeInt(_result);
					return true;
				}
			}
			return super.onTransact(code, data, reply, flags);
		}
	}
	
	...
	
}

 

其實和我們上文的寫法是一樣的,自動生成的 IReporter 類自動給我們處理了一些參數的組包和解包而已,在 case 語句中調用了 this.report 即可調用到自己的業務邏輯部分了。

4.2 Driver

與上文一致,還是 Binder 的內部封裝。

4.3 Client

MainActivity.java

private IReporter mReporterAidl;
private class AidlConnection implements ServiceConnection {

	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		mReporterAidl = IReporter.Stub.asInterface(service);
	}
	
	@Override
	public void onServiceDisconnected(ComponentName name) {
		mReporterAidl = null;
	}
}
...

@Override
protected void onCreate(Bundle savedInstanceState) {
	...
	Intent intent = new Intent(this, AidlService.class);
	bindService(intent, new AidlConnection(), BIND_AUTO_CREATE);

}

這裡與手動實現方式也有區別,即調用了 Stub 對象的 asInterface,具體做了什麼呢?

public static com.android.binder.IReporter asInterface(android.os.IBinder obj)
{
	if ((obj==null)) {	
		return null;
	}
	
	android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
	if (((iin!=null)&&(iin instanceof com.android.binder.IReporter))) {
		return ((com.android.binder.IReporter)iin);
	}
	
	return new com.android.binder.IReporter.Stub.Proxy(obj);
}

先查找本地接口是否存在,判斷是否是本地調用,如果是則直接返回 IReporter 的對象,否則返回 Stub.Proxy 對象,這個 Proxy 對象是做什麼的呢?

private static class Proxy implements com.android.binder.IReporter
{
	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 report(java.lang.String values, int type) 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.writeString(values);
			_data.writeInt(type);
			mRemote.transact(Stub.TRANSACTION_report, _data, _reply, 0);
			_reply.readException();
			_result = _reply.readInt();
		}
		
		finally {
			_reply.recycle();
			_data.recycle();
		}
		return _result;
	}
}

基本上已經很明了了,就是一個代理對象,對調用接口參數做組包而已,然後調用了 mRemote.transact 接口,和上文手動實現的方式是一致的。

4.4 小結

  • AIDL 自動生成了 Stub 類
  • 在 Service 端繼承 Stub 類,Stub 類中實現了 onTransact 方法實現了「解包」的功能
  • 在 Client 端使用 Stub 類的 Proxy 對象,該對象實現了「組包」並且調用 transact 的功能

有了 AIDL 之後,IReporter 接口就變得有意義了,Client 調用接口,Server 端實現接口,一切「組包」、「解包」的邏輯封裝在了 Stub 類中,一切就是那麼完美。

2020年9 月 posted by admin in 文獻參考 and have No Comments

Place your comment

Please fill your data and comment below.
名稱:
信箱:
網站:
您的評論: