Service與Android系統設計(6)--- Native Service
Native Service
Native Service,這是Android系統裡的一種特色,就是通過C++或是C程式碼寫出來的,供Java進行遠端呼叫的Remote Service,因為C/C++程式碼生成的是Native程式碼(機器程式碼),於是叫Native Service。隨著Android系統的效能需求越來越高,Native Service需求將越來越高。
Native Service的實現,相當於RemoteService的hack版,相當於直接將Remote Service裡在Java與C++程式碼之間的互動設計偷換掉。Java程式碼走到JNI實現的BinderProxy物件的transact()方法之後,便直接進入到Native實現BBinder物件,然後一直通過IPCThreadState物件傳送Binder訊息,在另一個實現了對應IBinder介面的程序空間裡的IPCThreadState物件會接收到這一Binder訊息,再通過JNI回撥到Java環境裡的Binder物件所實現的onTransact()方法,於是就得到Remote Service。
如果需要通過Native程式碼來提供這樣的服務,實際上也很簡單,從IBinder介面的Stub端物件的原理可以看出,如果我們在回撥Java的JNI之前將程式碼呼叫截斷,直接通過Native程式碼來實現onTransact()方法,則可以完成Service的Stub端實現。同時,出於實現角度的考慮,RemoteService介面不光可以服務於Java環境,也可以同時服務於Native環境,於是我們也可以提供Native環境裡的BinderProxy程式碼,就可以直接通過BpBinder物件的transact()方法來發送Binder訊息,此時就可以Native環境裡的Proxy端。於是Native環境下的Binder程式設計便如下圖所示:
與Java環境裡的Stub.Proxy物件的實現相對應,在Native態也會定義BpXXX物件,其中B代表Binder,p程式碼Proxy,所需要的介面名為XXX。因為所需要傳送的Binder通訊都是經由BpBinder::transact()方法傳送,於是Java環境與Native環境的Proxy在本質上是一回事,只是提供不同程式語言環境裡的不同實現而已。同時,在接收與處理端,IPCThreadState物件回撥到BBinder引用的onTransact()時,此時的BBinder引用的並不再是一個JavaBBinder物件,而是拓展出來的BnXXX物件,n代表Native。於是BBinder::transact()就會直接呼叫BnXXX裡實現的onTransact(),在這一onTransact()方法裡就可以處理Binder訊息,並將結果返回。
libbinder支援Native Service
如果手動依次去實現支援Binder互動的類也是可以的,但這樣會造成程式碼的重複,程式設計經驗也會告訴我們,重複性的程式碼更容易造成因為不小心而引發的錯誤,也會造成升級上的困難。為了解決這樣的麻煩,在Native Service機制的實現上,也會像Java環境裡的AIDL程式設計那樣,儘可能使用重用來進行。從libbinder的基本實現,我們也可以看出,完成重用的第一步就是實現libbinder領域的概念對映,於是對於Java環境裡的IInterface、Binder、IBinder,在C++實現的libbinder也同樣會有。Java編寫的Remote Service,是通過IInterface的asBinder()返回IBinder引用,通過IBinder引用在Proxy與Stub的功能不同而返回不同物件來實現。於是C++環境裡的libbinder也將會是如此,為了保持跟Java環境相容,Binder必然會是通過IInterface來完成遠端訪問,通過IInterface的asBinder()來返回IBinder,對於客戶端返回的是Proxy物件,對於Stub端會返回Stub物件。但因為C++語言是基於指標,而並非像Java那樣基於物件的引用來訪問,在具體的實現上會稍微有點區別。與Binder的遠端呼叫相關的標頭檔案定義全部來自於IInterface.h:
namespace android {
class IInterface : public virtual RefBase 1
{
public:
IInterface();
sp<IBinder> asBinder();
sp<const IBinder> asBinder() const;
protected:
virtual ~IInterface();
virtual IBinder* onAsBinder() =0;
};
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj) 2
{
returnINTERFACE::asInterface(obj);
}
template<typename INTERFACE> 3
class BnInterface : public INTERFACE, public BBinder
{
public:
virtualsp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual constString16& getInterfaceDescriptor()const;
protected:
virtual IBinder* onAsBinder();
};
template<typename INTERFACE> 4
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(constsp<IBinder>& remote);
protected:
virtual IBinder* onAsBinder();
};
- IInterface,介面類。跟Java環境一下,用於提供asBinder()方法,返回一個IBinder引用。但與Java環境不同之處在於,Java是一種強型別語言,必須通過引用來訪問到物件,可以安全地使用,而在C++環境裡,物件則是直接通過指標來進行訪問,有可能會因為IInterface這個介面類所依託的物件不存在而出現段錯誤。於是IInterface的asBinder()介面被拆分成兩部分,外部介面asBinder(),和內部介面onAsBinder()。asBinder()會通過判斷this指標是否有空來決定呼叫onAsBinder()返回IBinder引用,或是返回空指標NULL。具體實現IInterface介面類時,則是通過實現onAsBinder()來完成,C++沒有介面類概念,於是onAsBinder()作為純虛擬函式則使用IInterface成為介面類。
- interface_cast()方法。Java環境裡的IBinder會有asInterface()介面方法,在libbinder裡通過C++實現的IBinder則不能提供這一介面,於是需要通過一個全域性有效的interface_cast()巨集來完成這一功能。interface_cast()是呼叫一個尚未定義的INTERFACE::asInterface()巨集,於是只會在有明確定義asInterface()的地方,interface_cast()才會有效。
- BnInterface,實現Stub功能的模板。它使用的模板引數INTERFACE是用於繼承之用,每個通過BnInterface模板生成的類都會繼承自IINTERFACE,從而獲得該INTERFACE裡定義。同時,BnInterface還會繼承自BBinder類,這決定了通過BnInterface得到的物件必然是完成Stub功能,通過拓展onTransact()方法來實現Binder命令的解析與執行。
- BpInterface,實現Proxy功能的模板。同BnInterface一樣,BpInterface模板也是使用INTERFACE作模板引數,並繼承自它,於是BpInterface與BnInterface必然需要實現INTERFACE所需要的介面方法。同時BpInterface又會繼承BpRefBase,於是決定了基於BpInterface會得到Proxy功能,實現INTERFACE裡宣告的介面方法,並將最終的呼叫對映到對應IBinder的transact()裡完成Binder命令的傳送,並處理通過Binder返回的結果。
IInterface介面類(通過純虛擬函式實現),BnInterface模板,跟BpInterface模板,就構成與Java環境幾乎一致的Binder支援環境。這三者本身貌似並不直接關聯到一起,如果新建一個繼承自IInterface的介面類,把所需要實現的介面方法在這個類進行定義,然後把這個類作為模板引數分別傳入BnInterface和BpInterface模板,就會得到跟Java環境裡類似的介面類定義的效果。因為 AIDL不支援C++環境,於是這種模板技巧則可以減小對於Binder操作上的重複程式碼。現在我們得到的Native Service相關的類關係圖如下:
這並非全部,在Java環境裡實現IInterface掊口類,一般會指定一個final限定的descriptor,這個descriptor字串將限定當前遠端介面在Binder環境裡的唯一性,C++環境裡也需要這樣Binder標識。同時,還有一些固定程式碼,在C++環境裡也可以通過巨集來減小程式碼重複。在這裡需要通過一個未知物件來得到一些具體的程式碼,通過模板實現會過於複雜,通過巨集來完成則只需要簡單的字元拼接,所以,在IInterface.h裡還定義如下的三個巨集:
#define DECLARE_META_INTERFACE(INTERFACE) \ 1
static constandroid::String16 descriptor; \
staticandroid::sp<I##INTERFACE> asInterface( \
constandroid::sp<android::IBinder>& obj); \
virtual constandroid::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE(); \
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \ 2
const android::String16I##INTERFACE::descriptor(NAME); \
constandroid::String16& \
I##INTERFACE::getInterfaceDescriptor() const { \
returnI##INTERFACE::descriptor; \
} \
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>&obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr =static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL){ \
intr = newBp##INTERFACE(obj); \
} \
} \
return intr; \
} \
I##INTERFACE::I##INTERFACE(){ } \
I##INTERFACE::~I##INTERFACE(){ } \
#define CHECK_INTERFACE(interface, data, reply) \ 3
if(!data.checkInterface(this)) { return PERMISSION_DENIED; } \
- DECLARE_META_INTERFACE,用於定義通用方法,像descriptor的成員變數、asInterface()等。與Java環境不同,C++會將標頭檔案與實現的C++檔案分開存放,於是這些方法必須會拆分成定義與實現兩部分。
- IMPLEMENT_META_INTERFACE,用於實現DECLARE_META_INTERFACE裡定義的通用方法。於是descriptor會作為引數被傳入,同時通用方法也會被實現,像構造方法、析構方法。唯一特殊的部分是asInterface()方法的實現,跟Java環境裡一樣,這一方法必須要根據傳入IBinder引用,建立IBinder所對應的Proxy物件。但這裡的通用程式碼並不知道所需要的Proxy物件會是什麼,所以會限定死程式碼。asInterface()方法在IBinder的本地引用不存在的情況下,會建立一個Bp##INTERFACE物件,這一物件會通過傳入的巨集引數INTERFACE來指定,比如我們需要實現一個叫Task的介面,則其通過asInterface()方法返回的Proxy物件只會是BpTask物件。
- CHECK_INTERFACE,這簡單的一行,是通過引數的data這一parcel來驗證當前是否對遠端訪問有許可權。
上述三個巨集是用於不容易使用模板實現,僅適用於IInterface介面類的通用方法。針對於BnInterface和BpInterface模板類,也可以使用模板來實現其各自的通用方法:
template<typename INTERFACE>
inline sp<IInterface>BnInterface<INTERFACE>::queryLocalInterface(
const String16& _descriptor)
{
if(_descriptor == INTERFACE::descriptor) return this;
return NULL;
}
template<typename INTERFACE>
inline const String16&BnInterface<INTERFACE>::getInterfaceDescriptor() const
{
return INTERFACE::getInterfaceDescriptor();
}
template<typename INTERFACE>
IBinder*BnInterface<INTERFACE>::onAsBinder()
{
return this;
}
template<typename INTERFACE>
inlineBpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
:BpRefBase(remote)
{
}
template<typename INTERFACE>
inline IBinder*BpInterface<INTERFACE>::onAsBinder()
{
return remote();
}
上面的模板方法,都是可以通過一個INTERFACE模板引數所能容易得到的。比如通過INTERFACE來得到descriptor。另外,onAsBinder()是IInterface類裡定義的純虛擬函式,需要具體實現。BnInterface裡提供的onAsBinder()實現很簡單,直接返回當前物件指標即可,而BpInterface提供的onAsBinder()實現要複雜一些,會呼叫remote()取回BpRefBase物件,而這一BpRefBase是根據自己的構造方法來得到的。最終,BnInterface和BpInterface的引用便會通過同一onAsBinder()介面方法的不同實現關聯到一起。這些通用模板方法,雖然使用者不需要改動,也並不關心,但會被自動填充到對應的實現裡,有效減小了編寫Native Service時的工作量。
實現一個簡單的Native Service
通過IInterface裡定義的這些不太好理解的模板方法,使得編寫一個Native Service的工作量也並不大。比如在前面例子裡編寫出的Java環境裡的Remote Service,如果要轉換成Native Service,則只需要比較簡單的實現即可。從最基本的實現需求來說,基本上是定義一個新的基於IInterface介面類,填充到NativeService涉及的類關係裡,得到的如下的繼承關係:
在Native Service的類的繼承關係上,所有加了顏色標識的部分,ITask介面類、BpInteface<ITask>和BnInterface<ITask>模板類、以及進一步派出來的BpTask和BnTask,都是需要手動實現的。這樣實現最終得到的結果,就是我們可以通過一個繼承自BnTask的可例項化的類TaskService構造一個物件,這一物件便可以在其生存期內響應與處理Binder命令的請求。而通過中間引入的一層BpTask,也可以自動地將客戶端程式碼通過BpRefBase的引用自動生成。
作為所有實現的源頭,於是我們需要定義ITask介面類:
class ITask: public IInterface {
public:
enum {
TASK_GET_PID = IBinder::FIRST_CALL_TRANSACTION,
};
virtual int getPid(void ) = 0;
DECLARE_META_INTERFACE(Task);
};
IMPLEMENT_META_INTERFACE(Task, "Task");
作為兼用於Proxy與Stub的介面類定義的IInterface,實際上至少還需要定義Binder命令、asBinder()等基本方法。這時,我們就可以看到在IInterface定義裡使用巨集的好處了,我們這裡只需要定義屬於特性的部分,比如這一Native Service所需要的Binder命令,和支援這一Binder命令的遠端介面方法,僅此而已。而其他部分的程式碼,並不需要我們直接實現,我們只需要使用DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE這兩個巨集來自動化生成這些共性的部分。於是,實現一個遠端介面類,會是通過繼承IInterface介面類,然後得到具體的介面類,比如我們這裡的ITask。剩下的部分,會是如下四路的基本套路:
- 1) 定義Binder命令,而且命令都以IBinder::FIRST_CALL_TRANSACTION,也就是1開始;
- 2) 定義介面方法,也就是一個純虛方法,比如getPid();
- 3) 通過DECLARE_META_INTERFACE,從而自動生成這一介面類所需要的通用屬性與方法;
- 4) 使用IMPLEMENT_META_INTERFACE,自動提供第3)裡所缺少方法的實現。
有了具有共性的介面類定義之後,剩下的就是分別實現Proxy與Stub端了。我們可以先來看Proxy端的實現,從IInterface裡的定義裡可以看到,同樣出於程式碼重用的目的,Proxy端的程式碼都將源自BpInterface模板類,只需要實現介面方法即可。我們通過BpInterface模板套用到介面ITask之上,就得到了我們需要的BpTask類,然後在BpTask類裡實現具體的介面方法,把介面方法轉換成Binder命令的傳送操作,Proxy端的實現便完成了。
class BpTask : public BpInterface<ITask> {
public:
BpTask(const sp<IBinder>& impl) : BpInterface<ITask>(impl) { }
virtual void getPid(int32_tpush_data) {
Parcel data, reply;
data.writeInterfaceToken(ITask::getInterfaceDescriptor());
data.writeInt32(push_data);
remote()->transact(TASK_GET_PID, data, &reply);
int32_t res;
status_t status = reply.readInt32(&res);
return res;
}
}
在BpTask類實現裡,我們也可以拓展其構造或是析構方法來處理一些私有屬性。但最重要的是,一定要在BpTask類裡通過虛擬繼承實現所有的介面方法,比如我們在ITask接口裡定義的getPid(),並且在這一方法裡將該方法轉換成Binder命令的傳送與返回值的處理。除此之外,其他具有共性的部分,都會由系統來完成,比如BpInterface會繼承自BpRefBase,通過remote()方法返回的也是BpRefBase,但BpRefBase最終會引用到BpBinder物件,最終通過這一BpBinder命令將Binder命令傳送出去。
對於Stub端的實現,無論是Java環境裡還是C++實現的Native環境,所有的訊息分發處理的介面都是onTransact()回撥方法。於是,整個Stub端實現就會是基於BnInterface模板,然後再覆蓋其onTransact()方法。對於我們例子裡的BnTask,由會有如下的實現:
class BnTask : publicBnInterface<ITask> {
virtual status_tonTransact(uint32_t code,const Parcel& data,
Parcel* reply,uint32_t flags = 0);
};
status_t BnTask::onTransact(uint32_t code, const Parcel&data,
Parcel* reply,uint32_t flags) {
CHECK_INTERFACE(ITask, data, reply);
switch(code) {
case TASK_GET_PID: {
int32_tpid = getPid();
reply->writeInt32(pid);
return NO_ERROR;
} break;
default:
returnBBinder::onTransact(code, data, reply, flags);
}
}
BnTask這個Stub實現,BnInterface模板套用到ITask介面類上得到的類,然後再實現自己的onTransact()。在我們這個例子裡將BnTask定義與onTranscat()實現分開來,但把兩者寫到一起是一回事。在前面的Binder底層分析裡我們知道, IPCThreadState物件會捕獲底層的Binder命令,然後回撥到onTransact()方法處理這一命令,於是在onTranscat()裡只需要根據傳入的code進行命令的分發與處理即可。比如我們例子裡,當檢查到命令的code是TASK_GET_ID,也就是我們在ITask介面類裡定義的Binder命令,會根據這一命令呼叫到getPid()方法,然後再將結果通過reply這一Parcel傳回給呼叫端,這樣就完成了遠端呼叫的實現。最後,縷縷進行一次IoC反向呼叫到父類的onTransact(),從而可以處理一些通用的Binder命令。
但到此,我們的Native Service實現並不完整,我們在Stub端並沒有提供getPid()方法的實現,這樣BnTask這個類是無法被例項化成具體的物件,從而響應遠端請求。於是,我們通過繼承BnTask,然後再實現介面方法getPid(),這一新的物件便可被例項化成為一個具體的物件。這樣做的目的進一步將Binder處理相關程式碼與具體的實現拆分開,我們可以得到在命名和實現上更加清晰的TaskService:
class TaskService : public BnTask {
virtual int32_tgetPid() {
return getpid();
}
};
TaskService繼承自BnTask,又提供了所需要的介面方法,於是任何被系統排程到的可執行部分,無論是執行緒還是程序,都可以通過例項化一個TaskService物件響應遠端的getPid()的呼叫請求。在程式碼的實現角度,一般ITask會在一個獨立的原始檔存放,以標明這是一個介面類,而TaskService會列入到另一個TaskService.cpp以說明這一個Service的具體實現。雖然不是強制要求,但這樣的實現會更符合android裡的編碼習慣,更容易維護。
但需要注意的是,ITask介面類會通過IMPLEMENT_META_INTERFACE()這個巨集來設定descriptor,於是在整個系統環境裡, 由某個descriptor來標識的Service必然會是唯一的。比如某個程序已經通過自己的ProcessState加入到了Binder域,則加入這一新加入的Binder處理只需要如下的簡單兩行,一行建立TaskService物件並加入到ServiceManager的管理,另一行啟動TaskService的Binder執行緒:
defaultServiceManager()->addService(String16("Task"),new TaskService());
android::ProcessState::self()->startThreadPool();
如果並不希望在某個已經開始處理Binder的程序裡執行 TaskService,也可以通過在當前程序空間裡初始化自己的ProcessState來完成Binder處理環境的初始化,然後再加入TaskService。比如,使用一個最簡單的C版本的程序來執行TaskService,只需要下面的一個簡單的C函式,編譯成可執行檔案並放到Android系統環境裡執行即可:
int main(int argc, char **argv)
{
defaultServiceManager()->addService(String16("PokeService"), new
PokeService());
ProcessState::self()->startThreadPool();
android::ProcessState::self()->startThreadPool();
LOGI("Pokeservice is now ready");
IPCThreadState::self()->joinThreadPool();
return 0;
}
如果程式碼裡任何地方需要使用到這一新加入系統的TaskService遠端服務,只需要通過ServiceManager來查詢到這一服務對應的IBinder,然後再通過interface_cast()巨集得得IBinder所對應的Proxy物件,之後就可以直接進行看上去像是在本地執行的遠端呼叫了。比如,使用TaskService,則可以使用下面的簡單的程式碼即可完成:
sp<IServiceManager> sm =defaultServiceManager();
sp<IBinder> binder =sm->getService(String16("Task"));
sp<ITask> mTask =interface_cast<ITask>(binder);
LOGI("Task Service get %d",mTask.getPid());
同樣的執行需求,跟Java環境裡的aidl程式設計是一致的,只不過在目前的實現裡Java環境並沒有Binder端傳送的處理邏輯,如果我們希望我們這一新建的Native Service可以同時響應Java環境與Native環境的執行請求,可以新建一個Java實現的Proxy端程式碼即可,這時我們需要一個IXXX的介面類,也需要一個基於XXX.Stub.Proxy的Proxy類。同樣的道理,如果我們期望從C的程式碼回撥到Java實現的Remote Service,則也可以通過實現繼承自BpInterface的類即可,忽略掉或是實現一個並不會被用到BnInterface的部分,也會是有效的。
為了減小Native Service在程式設計上的工作量,在Binder裡還會有另一個BinderService相關的實現,也會使用模板的方式進一步節省程式碼的工作量,見frameworks/base/include/binder/BinderService.h:
namespace android {
template<typename SERVICE>
class BinderService
{
public:
static status_tpublish(bool allowIsolated =false) {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()),new SERVICE(),allowIsolated);
}
static voidpublishAndJoinThreadPool(bool allowIsolated =false) {
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SERVICE::getServiceName()), new SERVICE(),allowIsolated);
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
static void instantiate() {publish(); }
static status_tshutdown() {
return NO_ERROR;
}
};
使用這一BinderService模板,便我們的程式碼進一步被簡化,比如我們在Android系統內見到不會接觸到IPCThreadState和ProcessState物件,在定義NativeService時,一般會使用這樣的方式,比如AudioFlinger:
class AudioFlinger :
publicBinderService<AudioFlinger>,
public BnAudioFlinger
{
…
}
於是在某個程序裡,我們就只需要最簡單一行:
AudioFlinger::instantiate();
NativeService是由libbinder提供的一種機制,相當於是Java環境Remote Service的底層”Hack”實現。而通過Native Service,我們得到Java環境所不具備的一些新的特質:
- 1) 效能更高。效能上的差異性取決於執行時的不同上下文環境,但通常來說,Native程式碼總會有比Java這種解釋型語言高得多的執行效率。
- 2) 使用同一種語言程式設計,更加容易理解與除錯。Java語言不能直接進行系統呼叫,必須透過JNI來呼叫C程式碼來訪問系統功能。而NativeService則完全不同,C++具備直接進行系統呼叫的能力,於是在訪問作業系統或是硬體功能時,不再需要JNI,可以直接進行呼叫,程式碼實現上會更加統一。
- 3) 自動化GC,Native態的Binder,與Java協作時被自動切入到Dalvik虛擬機器的GC管理,也能使用類似於Java環境的自動化垃圾回收。同時,這種GC機制可以通過RefBase進行進一步拓展。
- 4) 缺點:不能使用AIDL,編碼工作量更大。
- 5) 缺點:跟Java的Binder域程式設計環境功能重疊,也有可能會出錯。比如Binder命令的定義,在Java與Native Service互動時,在Java環境與C++環境都要有各自一份拷貝。
綜合所有的這些因素,雖然Native Service有一定的侷限性,但帶來的好處要更多。於是在Android的版本變更過程中,NativeService使用越來越普遍。