Android10?Binder原理概述深入解析
IPC工具介紹
Binder作為Android 眾多的IPC通訊手段之一,在Framework的數(shù)據(jù)傳輸中起到極為關(guān)鍵的作用。為什么Google需要重新創(chuàng)造Binder這么一個(gè)IPC工具,使用linux默認(rèn)提供的Pipe、Socket、共享內(nèi)存、信號(hào)、消息隊(duì)列等IPC工具不行嗎?
答案是 這些傳統(tǒng)的linux IPC工具有一部分android也在使用,只是在某些場(chǎng)合下它們無(wú)法滿足需求,所以才創(chuàng)造了Binder這么一個(gè)工具。 為了更好地向各位讀者解釋為什么需要Binder,我們先來(lái)簡(jiǎn)單地認(rèn)識(shí)一下linux傳統(tǒng)的IPC工具,讓大家對(duì)它們的優(yōu)勢(shì)和劣勢(shì)有一個(gè)更為直觀的認(rèn)識(shí)。
Pipe
管道是一種最基本的IPC工具,作用于有血緣關(guān)系的進(jìn)程之間,完成數(shù)據(jù)傳遞。調(diào)用pipe系統(tǒng)函數(shù)即可創(chuàng)建一個(gè)管道。它有如下特質(zhì):
- 其本質(zhì)是一個(gè)偽文件(實(shí)為內(nèi)核緩沖區(qū))
- 由兩個(gè)文件描述符引用,一個(gè)表示讀端,一個(gè)表示寫端。
- 規(guī)定數(shù)據(jù)從管道的寫端流入管道,從讀端流出,一般只能單向通信,雙向通信需建立兩個(gè)管道。
- 只能用于父子、兄弟進(jìn)程(有共同祖先)間通信。
- 數(shù)據(jù)一旦被讀走,便不在管道中存在,不可反復(fù)讀取。 因此,管道的局限性表現(xiàn)得非常明顯,它并不適合一對(duì)多的方式建立通訊(盡管技術(shù)上能夠?qū)崿F(xiàn)),原因在于第5條,管道中的數(shù)據(jù)無(wú)法反復(fù)讀取。類似的還有FIFO(命名管道),它在管道的基礎(chǔ)上做了升級(jí),擺脫了第4條的共同祖先的限制,但仍要面臨一對(duì)多通訊的困境。
framework中有沒有使用Pipe進(jìn)行通訊?答案是有,但是用的很少,相比之下用的更多的是FIFO!!各位讀者如果感興趣的話,可以在源碼中搜一下 TransferPipe這個(gè)類,在其中可以找到Pipe的痕跡。
Sign
信號(hào)是由用戶、系統(tǒng)或者進(jìn)程發(fā)送給目標(biāo)進(jìn)程的信息,以通知目標(biāo)進(jìn)程某個(gè)狀態(tài)的改變或系統(tǒng)異常。linux系統(tǒng)已經(jīng)預(yù)置了一部分信號(hào)標(biāo)識(shí),它們都有著特殊的含義,部分信號(hào)如下所示:
- SIGHUP:本信號(hào)在用戶終端結(jié)束時(shí)發(fā)出,通常是在終端的控制進(jìn)程結(jié)束時(shí),通知同一會(huì)話期內(nèi)的各個(gè)作業(yè),這時(shí)他們與控制終端不在關(guān)聯(lián)。比如,登錄Linux時(shí),系統(tǒng)會(huì)自動(dòng)分配給登錄用戶一個(gè)控制終端,在這個(gè)終端運(yùn)行的所有程序,包括前臺(tái)和后臺(tái)進(jìn)程組,一般都屬于同一個(gè)會(huì)話。當(dāng)用戶退出時(shí),所有進(jìn)程組都將收到該信號(hào),這個(gè)信號(hào)的默認(rèn)操作是終止進(jìn)程。此外對(duì)于與終端脫離關(guān)系的守護(hù)進(jìn)程,這個(gè)信號(hào)用于通知它重新讀取配置文件。
- SIGINT:程序終止信號(hào)。當(dāng)用戶按下CRTL+C時(shí)通知前臺(tái)進(jìn)程組終止進(jìn)程。
- SIGQUIT:Ctrl+\控制,進(jìn)程收到該信號(hào)退出時(shí)會(huì)產(chǎn)生core文件,類似于程序錯(cuò)誤信號(hào)。
- SIGILL:執(zhí)行了非法指令。通常是因?yàn)榭蓤?zhí)行文件本身出現(xiàn)錯(cuò)誤,或者數(shù)據(jù)段、堆棧溢出時(shí)也有可能產(chǎn)生這個(gè)信號(hào)。
- SIGTRAP:由斷點(diǎn)指令或其他陷進(jìn)指令產(chǎn)生,由調(diào)試器使用。
- SIGABRT:調(diào)用abort函數(shù)產(chǎn)生,將會(huì)使程序非正常結(jié)束。
- SIGBUS:非法地址。包括內(nèi)存地址對(duì)齊出錯(cuò)。比如訪問(wèn)一個(gè)4個(gè)字長(zhǎng)的整數(shù),但其地址不是4的倍數(shù)。它與SIGSEGV的區(qū)別在于后者是由于對(duì)合法地址的非法訪問(wèn)觸發(fā)。
- SIGFPE:發(fā)生致命的算術(shù)運(yùn)算錯(cuò)誤。
- SIGKILL:用來(lái)立即結(jié)束程序的運(yùn)行。不能被捕捉、阻塞或忽略,只能執(zhí)行默認(rèn)動(dòng)作。
信號(hào)只能起到對(duì)進(jìn)程的通知作用,它無(wú)法發(fā)送復(fù)雜的數(shù)據(jù)類型,不適合用于進(jìn)程間的數(shù)據(jù)交換。
信號(hào)在整個(gè)framework中也扮演了極為重要的角色,各位讀者可以通過(guò)搜索sigemptyset、sigaddset等關(guān)鍵字,在源碼中找到它們的身影。
message queue
消息隊(duì)列,Unix的通信機(jī)制之一,可以理解為是一個(gè)存放消息(數(shù)據(jù))容器。將消息寫入消息隊(duì)列,然后再?gòu)南㈥?duì)列中取消息,一般來(lái)說(shuō)是先進(jìn)先出的順序。消息隊(duì)列本質(zhì)上是位于內(nèi)核空間的鏈表,鏈表的每個(gè)節(jié)點(diǎn)都是一條消息。每一條消息都有自己的消息類型,消息類型用整數(shù)來(lái)表示,而且必須大于 0。每種類型的消息都被對(duì)應(yīng)的鏈表所維護(hù)。

其中數(shù)字 1 表示類型為 1 的消息,數(shù)字2、3、4 類似。彩色塊表示消息數(shù)據(jù),它們被掛在對(duì)應(yīng)類型的鏈表上。
消息隊(duì)列的缺陷在于:容量受到系統(tǒng)限制;消息隊(duì)列的發(fā)送方與接收方?jīng)]有強(qiáng)關(guān)聯(lián)性,容易造成發(fā)送方往消息隊(duì)列中存放了消息,沒有接收方來(lái)取消息或接收方?jīng)]有及時(shí)取消息的問(wèn)題,消息的及時(shí)性無(wú)法保障。
目前在Android 10的非內(nèi)核源碼范圍內(nèi),沒有發(fā)現(xiàn)使用消息隊(duì)列。
shared memory
共享內(nèi)存就是映射一段能被其他進(jìn)程所訪問(wèn)的內(nèi)存,這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建,但多個(gè)進(jìn)程都可以訪問(wèn)。共享內(nèi)存是最快的 IPC 方式,它是針對(duì)其他進(jìn)程間通信方式運(yùn)行效率低而專門設(shè)計(jì)的。它往往與其他通信機(jī)制,如信號(hào)量,配合使用,來(lái)實(shí)現(xiàn)進(jìn)程間的同步和通信。
共享內(nèi)存利用內(nèi)存緩沖區(qū)直接交換信息,無(wú)須復(fù)制,快捷、信息量大是其優(yōu)點(diǎn)。但是共享內(nèi)存的通信方式是通過(guò)將共享的內(nèi)存緩沖區(qū)直接附加到進(jìn)程的虛擬地址空間中來(lái)實(shí)現(xiàn)的,因此,這些進(jìn)程之間的讀寫操作的同步問(wèn)題操作系統(tǒng)無(wú)法實(shí)現(xiàn)。必須由各進(jìn)程利用其他同步工具解決,開發(fā)上手難度較高,容易出錯(cuò),且存在數(shù)據(jù)安全隱患。
目前在Android 10的非內(nèi)核源碼范圍內(nèi),沒有發(fā)現(xiàn)使用共享內(nèi)存。
Socket
Socket這個(gè)不需要特別介紹了,不管是做C++開發(fā)還是Java開發(fā),都會(huì)涉及到套接字編程。相對(duì)其他的IPC方式,Socket是最適合做一對(duì)多這種通訊需求的。它的問(wèn)題在于數(shù)據(jù)需要經(jīng)過(guò)兩次拷貝,通訊效率相對(duì)低下。這個(gè)問(wèn)題在電腦等設(shè)備上都不是什么特別大的問(wèn)題,但考慮到Android搭載的移動(dòng)設(shè)備,尤其是早期的移動(dòng)設(shè)備,這個(gè)問(wèn)題就很致命了。
framework中當(dāng)然也存在Socket的使用痕跡,比如 system/core/init/init.cpp這個(gè)文件中就采用epoll機(jī)制,實(shí)現(xiàn)init進(jìn)程與其子進(jìn)程的通訊。
Android更看重的是效率和一對(duì)多通訊的問(wèn)題,無(wú)法采用傳統(tǒng)的IPC工具實(shí)現(xiàn),所以只能考慮自己另起爐灶。除此之外,傳統(tǒng)的IPC無(wú)法獲得對(duì)方進(jìn)程的PID\UID,從而無(wú)法鑒別對(duì)象的身份,從而會(huì)使Android系統(tǒng)的安全性無(wú)法得到保證(ps:無(wú)法獲得對(duì)方進(jìn)程的身份指的是Linux默認(rèn)沒有提供獲取通訊進(jìn)程的身份的接口,并不是說(shuō)采用傳統(tǒng)IPC沒有辦法實(shí)現(xiàn)這樣的安全管控需求,只是谷歌在綜合考慮了上述所有的因素的情況下,在共享內(nèi)存的基礎(chǔ)上做了一套新的解決方案)。
這里給各位讀者留個(gè)思考題,有興趣的讀者可以自己動(dòng)手去實(shí)驗(yàn)一下:
在一對(duì)多通訊的場(chǎng)景下,Binder的傳輸效率一定會(huì)比Socket高嗎?(提示:Socket包括BIO、NIO、NIO2、epoll等,請(qǐng)不要局限在BIO的通訊方式)
AIDL
AIDL 是 Android interface definition Language 的英文縮寫, 意思Android 接口定義語(yǔ)言,它與Binder有著千絲萬(wàn)縷的聯(lián)系。
AIDL是谷歌使用Java 編程語(yǔ)言的語(yǔ)法定義的專門服務(wù)于Binder IPC通訊的腳本語(yǔ)言,推出的根本原因是為了避免Binder通訊中大量模板代碼的書寫。AIDL腳本會(huì)在編譯期間,由Android SDK 工具生成基于該 .aidl 文件的 IBinder 接口,并將其保存到項(xiàng)目的 generated/ 目錄中。
我們來(lái)看一個(gè)簡(jiǎn)單的aidl文件:
packageackage com.example.commonservice;
// Declare any non-default types here with import statements
interface ITtsService {
void showTts(in String uid,in int textId,in boolean toPlayTts,in int type);
boolean isShowing();
}
它生成的java文件如下所示:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.commonservice;
// Declare any non-default types here with import statements
public interface ITtsService extends android.os.IInterface
{
/** Default implementation for ITtsService. */
public static class Default implements com.example.commonservice.ITtsService
{
@Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException
{
}
@Override public boolean isShowing() throws android.os.RemoteException
{
return false;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
{
private static final java.lang.String DESCRIPTOR = "com.example.commonservice.ITtsService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.commonservice.ITtsService interface,
* generating a proxy if needed.
*/
public static com.example.commonservice.ITtsService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.commonservice.ITtsService))) {
return ((com.example.commonservice.ITtsService)iin);
}
return new com.example.commonservice.ITtsService.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_showTts:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
boolean _arg2;
_arg2 = (0!=data.readInt());
int _arg3;
_arg3 = data.readInt();
this.showTts(_arg0, _arg1, _arg2, _arg3);
reply.writeNoException();
return true;
}
case TRANSACTION_isShowing:
{
data.enforceInterface(descriptor);
boolean _result = this.isShowing();
reply.writeNoException();
reply.writeInt(((_result)?(1):(0)));
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.commonservice.ITtsService
{
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 void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) 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.writeString(uid);
_data.writeInt(textId);
_data.writeInt(((toPlayTts)?(1):(0)));
_data.writeInt(type);
boolean _status = mRemote.transact(Stub.TRANSACTION_showTts, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().showTts(uid, textId, toPlayTts, type);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public boolean isShowing() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_isShowing, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().isShowing();
}
_reply.readException();
_result = (0!=_reply.readInt());
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.example.commonservice.ITtsService sDefaultImpl;
}
static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.example.commonservice.ITtsService impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.commonservice.ITtsService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
public boolean isShowing() throws android.os.RemoteException;
}
雖然是簡(jiǎn)短的一個(gè)aidl文件,但生成的模板代碼卻極為復(fù)雜,為了整理清楚這段代碼的結(jié)構(gòu),筆者先隱藏其部分內(nèi)容:
public interface ITtsService extends android.os.IInterface
{
/** Default implementation for ITtsService. */
public static class Default implements com.example.commonservice.ITtsService
{
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
{
private static class Proxy implements com.example.commonservice.ITtsService
{
}
static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
public boolean isShowing() throws android.os.RemoteException;
}
可以看到,代碼的結(jié)構(gòu)如同套娃一樣,一層接一層。各位讀者不妨思考一下,如果不這樣套娃,把ITtsService里的Default和Stub類移到外面來(lái),這樣做可不可以?
答案是,可以的,不過(guò)類的命名方式可能要稍微做一下修改,如ITtsService_Defalut、ITtsService_Stub,以便于引用上的區(qū)分。當(dāng)然,代碼的結(jié)構(gòu)不是重點(diǎn),雖然谷歌的方式可讀性會(huì)差一點(diǎn),但開發(fā)人員不需要直接和這些源碼打交道,也不是不可以接受。
Binder不一定都是跨進(jìn)程通訊,同樣也支持同進(jìn)程通訊,比如 Activity綁定Service,通過(guò)Binder實(shí)現(xiàn)數(shù)據(jù)傳輸。在同一進(jìn)程通訊的情況下,Stub類身兼兩職,因其implements了ITtsService,它可以作為客戶端的調(diào)用方;同時(shí),它也是服務(wù)端的實(shí)現(xiàn)方。而在跨進(jìn)程通訊的情況下,則由Proxy來(lái)?yè)?dān)任客戶端的調(diào)用方。
至于Default這個(gè)類的作用,暫時(shí)不明,無(wú)法找到相關(guān)的資料得知為什么谷歌要生成這么一個(gè)類。
在此,AIDL的介紹先告一段落,其中更多的細(xì)節(jié)將放到后續(xù)的文章中再做補(bǔ)充。
HIDL
HIDL的生命周期及其短暫,它從Android 8引入,然后在Android 10 立馬被 Stable AIDL 所取代,雖然沒啥存在感,但還是簡(jiǎn)單地提及一下吧。
HAL 接口定義語(yǔ)言(簡(jiǎn)稱 HIDL,發(fā)音為“hide-l”)是用于指定 HAL 和其用戶之間的接口的一種接口描述語(yǔ)言 (IDL)。HIDL 允許指定類型和方法調(diào)用(會(huì)匯集到接口和軟件包中)。從更廣泛的意義上來(lái)說(shuō),HIDL 是指用于在可以獨(dú)立編譯的代碼庫(kù)之間進(jìn)行通信的系統(tǒng)。
HIDL 旨在用于進(jìn)程間通信 (IPC)。進(jìn)程之間的通信采用 Binder 機(jī)制。對(duì)于必須與進(jìn)程相關(guān)聯(lián)的代碼庫(kù),還可以使用直通模式(在 Java 中不受支持)。
更多HIDL相關(guān)的資料,可以參考 source.android.google.cn/docs/core/a…
以上就是Android10 Binder原理概述深入解析的詳細(xì)內(nèi)容,更多關(guān)于Android10 Binder原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
LayoutAnimation給ListView中的item設(shè)置動(dòng)態(tài)出場(chǎng)效果(實(shí)例)
下面小編就為大家?guī)?lái)一篇LayoutAnimation給ListView中的item設(shè)置動(dòng)態(tài)出場(chǎng)效果(實(shí)例)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
詳談OnTouchListener與OnGestureListener的區(qū)別
下面小編就為大家?guī)?lái)一篇詳談OnTouchListener與OnGestureListener的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
Android開發(fā)實(shí)現(xiàn)自動(dòng)切換文字TextSwitcher功能示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)自動(dòng)切換文字TextSwitcher功能,結(jié)合實(shí)例形式詳細(xì)分析了Android使用TextSwitcher實(shí)現(xiàn)文字自動(dòng)切換的原理、實(shí)現(xiàn)方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-03-03
Android實(shí)現(xiàn)語(yǔ)音識(shí)別代碼
語(yǔ)音識(shí)別在android上使用起來(lái)很方便也很簡(jiǎn)單.但是有個(gè)前提條件,就是android機(jī)器上必須預(yù)先安裝google的語(yǔ)音搜索工具,今天我們就來(lái)詳細(xì)探討下2015-06-06
Android Viewpager實(shí)現(xiàn)無(wú)限循環(huán)輪播圖
這篇文章主要為大家詳細(xì)介紹了Android Viewpager實(shí)現(xiàn)無(wú)限循環(huán)輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
Android組件Activity的啟動(dòng)過(guò)程深入分析
這篇文章主要介紹了Android組件Activity的啟動(dòng)過(guò)程,Activity作為Android四大組件之一,他的啟動(dòng)沒有那么簡(jiǎn)單。這里涉及到了系統(tǒng)服務(wù)進(jìn)程,啟動(dòng)過(guò)程細(xì)節(jié)很多,這里我只展示主體流程。activity的啟動(dòng)流程隨著版本的更替,代碼細(xì)節(jié)一直在進(jìn)行更改,每次都會(huì)有很大的修改2023-04-04

