Android NDK中C++运行时库介绍

一般的Android应用程序都是用Java语言编写的,在Dalvik虚拟机或ART虚拟机中运行的。

但是,出于对性能的考虑,Android也允许使用JNI接口,直接调用原生(Native)程序。这些程序都是直接被编译成平台支持的汇编指令,效率自然比在虚拟机中运行的要高。不过,现在ART虚拟机出现了,JNI调用在性能方面的优势被大大缩减。

一般情况下,自己编写的JNI程序都是使用C语言编写的。但有时候,程序的逻辑过于复杂,也会选择用C++语言,使用面向对象的方法编写。或者,要程序中要使用一个复杂的现成代码库,而它是用C++编写的。

Google提供的Android NDK本身是支持C++语言的。那么,编译器是怎么知道你的代码是用C语言,还是用C++语言编写的呢?答案是看你源程序文件的扩展名。默认情况下,如果你的程序是以“.cpp”结尾的话,就用C++编译器对其进行编译;而如果是以“.c”结尾的话,则用C编译器进行编译。你也可以通过修改Android.mk文件中的变量“LOCAL_CPP_EXTENSION”来指定特定后缀的源代码文件,必须使用C++编译器编译。例如:

LOCAL_CPP_EXTENSION := .cxx .cpp .cc

如果在你模块中的Android.mk文件中,包含以上变量赋值语句的话,那么所有以“.cxx”、“.cc”和“.cpp”扩展名结尾的源码文件,都将使用C++编译器编译。

不过,编译过后的C++程序想要执行,还必须要有C++运行环境(C++ Runtime)的支持。

默认情况下,Android NDK会使用一个非常迷你的C++运行环境,称做“libstdc++”。这个运行环境几乎什么都没有,不支持异常和RTTI(RunTime Type Information,即运行时类型识别),甚至连C++标准库也没有。

不过除了这个缺省的C++运行环境之外,Android NDK还带了一些其它的C++运行环境,它们各有各的特长,在使用的时候也经常分不清到底最好要使用哪个。本文下面的部分,将对它们各自的特点做一个详细的说明。

但在正式介绍之前,先说几个基本概念。

首先是所谓的C++标准库。C++标准除了定义了语法之外,还包含了一个包罗万象的标准库,方便程序的开发者调用,从而使用简单代码就可以实现复杂的功能。最简单的,比如string就是属于C++标准库的,C++自己的语法中并没有string关键字,只有char。这个标准库非常庞大,包含的功能如下图所示:


接着,是所谓的C++运行时类型信息(RTTI)。它有点类似Java语言中反射的概念,就是可以在程序运行时,可以动态的判断某一个对象所属的类是什么。

然后是C++异常机制,这个简单,就是try和catch嘛。不过要想实现C++的异常捕获机制,需要C++编译器和运行时库一起配合才行,两者缺一不可。运行时库支持,但是C++编译器不支持也不行,反之亦然。

最后是所谓的动态库和静态库的概念。所谓动态库是程序运行时动态加载进进程内的,它的好处是如果程序中有很多独立的模块都需要使用同样的一个动态库,那么只需要在内存中加载一次就可以了。而静态库,是在模块编译的时候,在链接的过程中,将程序库中所需要的代码“拷贝”到模块内部实现的,它的坏处就是同样的代码会在各个模块中都存在,浪费内存和磁盘存储空间,甚至不同模块间的库函数代码会相互干扰,出现诡异的行为。

除去默认的那个C++运行时库,Android NDK中还带了其它的四个不同的C++运行时库,它们所支持的C++特性有可能都不一样,而且每个运行时库都分有动态库和静态库。下面这张表,总结了一下所有C++运行时库的特点:

下面简单介绍一下各个运行时库的特点:

1)libstdc++(默认运行时库)

前面也提到了,这个系统默认的C++运行时库功能非常的弱,基本上都不支持所有的C++高级特性。

不过,在真实的设备上,只自带了缺省的C++运行时库(一般该文件位于/system/lib/libstdc++.so),在Android NDK中只包含了这个运行时库所需要的一些头文件,并没有真实的静态库和动态库。所以,也就是说,Android系统中默认的那个C++运行时库是没有静态库版本的,而且由于系统自带了,所以在你的程序中也不需要再包含这个库的.so文件了,可以减小安装文件的大小。

2)gabi++

这个库也不支持C++标准库,但它加入了异常和RTTI的支持。

在Android NDK中,包含有这个库的静态和动态两个预先编译好的版本,位于<NDK Folder>/sources/cxx-stl/gabi++/目录下。

3)stlport

这个库提供了对C++所有特性的完整支持。它是开源项目STLPort(http://www.stlport.org)的一个Android移植版本。

在Android NDK中,也包含有这个库的静态和动态两个版本,位于<NDK Folder>/sources/cxx-stl/stlport/目录下。

同时,Android NDK中还包含了这个C++运行时库的所有源码,你可以在Application.mk文件中,加上下面的赋值语句来强制Android NDK通过源码编译这个库,而不要用预编译的版本:

STLPORT_FORCE_REBUILD := true

4)gnustl

这就是GNU标准C++运行时库(libstdc++-v3)。同样,它支持C++的所有特性,且在Android NDK中包括了静态和动态两个预编译的版本,位于<NDK Folder>/sources/cxx-stl/gnu-libstdc++/目录下。

5)llvm-libc++

最后一个就是llvm-libc++,它是LLVM libc++(http://libcxx.llvm.org/)的一个Android移植版本。也支持C++的所有特性,且也有静态和动态的两个预编译的版本,位于<NDK Folder>/sources/cxx-stl/llvm-libc++/目录下。

这两个预编译的版本都是使用Clang 3.4编译的。不过,和stlport一样,在Android NDK中也带有它的实现源码,可以在Application.mk文件中,加上下面的赋值语句来强制Android NDK通过源码编译这个库,而不要用预编译的版本:

LIBCXX_FORCE_REBUILD := true

那么多个C++运行时库,程序怎么知道到底用哪个呢?刚才说了,默认情况下,也就是不做任何特殊设置的时候,是使用libstdc++。如果想用别的C++运行时库,需要在你程序中的Application.mk文件中,对变量APP_STL进行赋值,想用哪个C++运行时库,就直接将运行时库的名字赋值给这个变量,而运行时库的名字就是上表第一列中的字符串。例如,如果想换用共享的gnustl库的话,需要在Application.mk文件中,加上下面的语句:

APP_STL := gnustl_static

还要特别说明的是,默认情况下,Android NDK的编译器的选项是不包含对C++异常和RTTI的支持的。刚才也提到过了,对异常和RTTI的支持,必须是编译器和C++运行时库相互配合才能完成的。如果编译器本身不支持,即使运行时库支持也没用。

所以,如果想让你的程序支持异常和RTTI,必须先让编译器支持,且选择合适的运行时库才行。

那么怎么让编译器支持异常和RTTI呢?如果你是想让你程序中的所有模块都支持,只需要在Application.mk文件中,对APP_CXXFLAGS变量赋值即可。例如,如果想全部程序加入对异常的支持,可以这样赋值:

APP_CPPFLAGS += -fexceptions

如果你只想让你程序中的某一个模块支持异常或RTTI,可以在模块所属的Android.mk文件中,对LOCAL_CPPFLAGS变量赋值即可。例如,下面的语句将会在编译你的指定模块时加上对RTTI的支持:

LOCAL_CPPFLAGS += -frtti

上面的语句也等效于下面这条语句:

LOCAL_CPP_FEATURES += rtti

最后,还有一点需要注意,如果你的JNI功能都是包含在一个模块(也就是程序中只有一个.so文件)中的话,可以考虑用静态库C++运行时库的形式。而如果你的程序中包含了多个模块,请尽量使用动态C++运行时库。

在用动态C++运行时库的时候,相应的动态库.so文件(默认的C++运行时库除外)会随你的程序一起发布出去,位于apk文件中的lib目录下。

如果你的目标Android系统时4.3之前,那么你必须要在加载所有你自己的模块之前,显式的加载C++动态运行时库。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值