Service与Android系统设计--- Native Service

Posted 宋宝华

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Service与Android系统设计--- 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();
;

  1. IInterface,接口类。跟Java环境一下,用于提供asBinder()方法,返回一个IBinder引用。但与Java环境不同之处在于,Java是一种强类型语言,必须通过引用来访问到对象,可以安全地使用,而在C++环境里,对象则是直接通过指针来进行访问,有可能会因为IInterface这个接口类所依托的对象不存在而出现段错误。于是IInterface的asBinder()接口被拆分成两部分,外部接口asBinder(),和内部接口onAsBinder()。asBinder()会通过判断this指针是否有空来决定调用onAsBinder()返回IBinder引用,或是返回空指针NULL。具体实现IInterface接口类时,则是通过实现onAsBinder()来完成,C++没有接口类概念,于是onAsBinder()作为纯虚函数则使用IInterface成为接口类。
  2. interface_cast()方法。Java环境里的IBinder会有asInterface()接口方法,在libbinder里通过C++实现的IBinder则不能提供这一接口,于是需要通过一个全局有效的interface_cast()宏来完成这一功能。interface_cast()是调用一个尚未定义的INTERFACE::asInterface()宏,于是只会在有明确定义asInterface()的地方,interface_cast()才会有效。
  3. BnInterface,实现Stub功能的模板。它使用的模板参数INTERFACE是用于继承之用,每个通过BnInterface模板生成的类都会继承自IINTERFACE,从而获得该INTERFACE里定义。同时,BnInterface还会继承自BBinder类,这决定了通过BnInterface得到的对象必然是完成Stub功能,通过拓展onTransact()方法来实现Binder命令的解析与执行。
  4. 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;        \\

  1. DECLARE_META_INTERFACE,用于定义通用方法,像descriptor的成员变量、asInterface()等。与Java环境不同,C++会将头文件与实现的C++文件分开存放,于是这些方法必须会拆分成定义与实现两部分。
  2. IMPLEMENT_META_INTERFACE,用于实现DECLARE_META_INTERFACE里定义的通用方法。于是descriptor会作为参数被传入,同时通用方法也会被实现,像构造方法、析构方法。唯一特殊的部分是asInterface()方法的实现,跟Java环境里一样,这一方法必须要根据传入IBinder引用,创建IBinder所对应的Proxy对象。但这里的通用代码并不知道所需要的Proxy对象会是什么,所以会限定死代码。asInterface()方法在IBinder的本地引用不存在的情况下,会创建一个Bp##INTERFACE对象,这一对象会通过传入的宏参数INTERFACE来指定,比如我们需要实现一个叫Task的接口,则其通过asInterface()方法返回的Proxy对象只会是BpTask对象。
  3. 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使用越来越普遍。


以上是关于Service与Android系统设计--- Native Service的主要内容,如果未能解决你的问题,请参考以下文章

Android系统服务分析与Native Service实例

Android系统服务分析与Native Service实例

最佳实践Android设计:隐藏的webview、fragment、service?

20172323 2017-2018-2《程序设计与数据结构》第十一周学习总结

20172323 2017-2018-2《程序设计与数据结构》第十一周学习总结

记一次课程设计(顺便复习一下android service和七牛云 android sdk)