特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。作者系LiAnLab.org资深Android技术顾问吴赫老师。本系列文章交流与讨论:@宋宝华Barry
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();
- 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使用越来越普遍。