Introduction:
Android NDK是一个开发工具,可以帮助Android应用开发人员嵌入由C文件和(或)C++文件编译生成的本地代码到第三方应用包中。
Android NDK只能在Android 1.5以上的平台(1.0和1.1系统不支持在释放1.5时对ABI和工具链的重要变更)。
Android NDK Goals
Android虚拟机允许你的应用程序调用由JNI实现的native code中的方法。简而言之,这意味着:
- 你的源码需要声明一个或多个带有
native关键字的方法来表示这些方法是通过native code来实现,比如:
native byte[] loadFile(String filePath);
- 你必须提供一个实现了这些方法的
native共享库,并将其打包到你的应用apk里面。这个库必须按照Unix的标准命名方式命名,如lib<something>.so,而且要包含一个标准的JNI入口,如:
libFileLoader.so
- 你的应用必须显示加载这个库。比如,在一打开应用时添加如下代码:
static { System.loadLibrary("FileLoader"); }
注意这里要去掉前缀lib和后缀.so
Android NDK是Android SDK的一个补充,可以帮助你:
- 生成
JNI兼容共享库,可以运行在ARM+Android1.5以上 - 将生成的共享库拷贝到你应用工程路径下的一个适当的位置,他们会被自动的添加到你最终的
apk中 - 在
NDK后面的修订中,我们将会提供通过一个远程gdb连接来调试native code的工具,并且提供尽可能多的source/symbol信息
另外,Android NDK提供:
- 一系列的交叉工具链(编译,链接,等等),可以生成
Linux、OS X、Windows with Cygwin的本地ARM字节码 - 一系列系统头文件,对应着
Android平台上支持的稳定的native APIS。这些定义会在后面平台更新时全部支持。可以在docs/STABLE-APIS.html中查找。
Android系统中的大多数的native系统库会在后面的平台更新中被禁用(frozen),也有可能被完全改掉甚至是删除。
- 允许开发者只写很少的代码告诉编译系统哪些源文件需要被编译以及如何变异。编译系统会处理所有的
toolchain/platform/CPU/ABI.另外,NDK的后续更新中会加大对toolchains,platforms,system的接口支持,并且不需要改变开发者的编译文件。
Android NDK Non-Goals
使用NDK编写运行在Android设备上的generic native code不是一个好的选择。特别的,你的应用应该仍然使用Java编程语言,正确的处理Android系统事件避免产生ANR对话框或者处理Android应用的生命周期。
由于在这个开发环境中的大部分操作对开发者有特定的要求,强烈建议开发者对JNI有一个很好的理解。包括:
- 不能直接通过
direct native pointers获取VM对象的内容。 - 在
native code想要在JNI调用之间保存VM对象的handles时要求明确的引用管理。
NDK只提供有限的Android平台支持的native API和库对应的系统头文件。当某个Android系统有很多的native共享库的时候,应该考虑到后续的平台更新有可能完全改变实现细节。
NDK development in practice
下面是教你如何使用Android NDK开发native code的大致流程:
- 把你的
native源码放到$PROJECT/jni/... - 编写
$PROJECT/jni/Android.mk,告诉NDK编译系统如何编译你的源码 - 编写
$PROJECT/jni/Application.mk,对编译系统更详细的描述你的工程。这个不是必须的,但是这允许你编译不止一种CPU架构或者重写编译器/链接器的标志(详细内容可以参考docs/APPLICATION-MK.html) - 在工程目录下执行
"$NDK/ndk-build"编译你的native code
执行成功后,最后一步会将你的应用需要的共享库放到应用的根目录下。然后就可以通过正常的方法生成你最终的apk
现在,进行一些更详细的说明:
- 配置
NDK:
老版本的
NDK要求你运行'build/host-setup.sh'脚本配置你的NDK环境,这一步操作在NDK r4时被完全移除了。
- 放置
C和C++源文件
把你的
native源文件放到下面的目录:
$PROJECT/jni/
$PROJECT对应着你的Android应用的工程目录
这里的目录命名和结构不影响最终生成application package,因此你不需要按照com.mycompany.myproject的命名方式。
注意:C和C++源文件均支持。NDK默认支持的的C++文件类型为'.cpp',但是其他类型的也支持(详见docs/ANDROID-MK.html)
调整Android.mk文件可以将你的源文件放在不同的位置
- 编写编译脚本
Android.mk:
Android.mk是一个小的脚本文件,用来告诉NDK编译系统如何编译你的源码。它的编写语法详见docs/ANDROID-MK.html。
简单地说,NDK将你的源码归并到"modules",每一个module可以如下中的一个:
-
a static library(静态库) -
a shared library(共享库)
你可以在一个Android.mk文件中定义多个modules,你也可以编写多个Android.mk文件,定义每一个module。
注意:一个Android.mk文件可能会被编译系统解析多次,因此不要假设某些变量没有被定义。默认情况下,NDK会编译下面的编译脚本:
$PROJECT/jni/Android.mk
如果你想在子文件夹下面定义Android.mk文件,你需要在最上层的Android.mk文件中显示的包含进来。可以使用帮助函数来实现,如:
include $(call all-subdir-makefiles)
这将会把当前编译文件路径下的所有子文件夹中的Android.mk文件包含进来。
- 编写
Application.mk编译脚本(可选):
Android.mk文件向编译系统描述你的modules,Application.mk文件描述应用本身。Application.mk脚本详情可以查看docs/APPLICATION-MK.html文档。这其中包括:
- 你的应用需要的
modules列表 - 需要生成机器码的
CPU架构 - 可选的信息,比如
release or debug编译,指定C或C++编译器flag以及其他一些在编译时应用到所有的modules的信息。
默认情况下
NDK会编译Android.mk中列出的所有的modules以及默认的CPU ABI架构(armeabi)。
Application.mk文件有两种使用方式:
- 将其放置到
$PROJECT/jni/Application.mk,'ndk-build'脚本会自动的将其加载 - 将其放置到
$NDK/apps/<name>/Application.mk,这里$NDK代表你的NDK安装路径。在这之后,在NDK目录下执行"make APP=<name>"。
第2种方法在
Android NDK r4之前使用的,目前考虑到兼容性也是支持的,但是强烈建议使用第一种方法,因为这个方法更简单且不需要修改或者改变NDK的安装目录的结构。
- 执行
NDK编译环境:
推荐使用在Android NDK r4以后引入的'ndk-build'脚本进行NDK机器码的编译。你也可以使用第二种依赖于在'$NDK/apps'创建子目录的方法进行编译。
在两种情况下,编译成功后会将最终striped后的二进制modules(如共享库)拷贝到你的应用的工程路径下。
Strip can remove debugging information and other data included in the executable which is not necessary for execution in order to reduce the size of the executable.(i.e. I had a 40MB executable that when stripped was reduced to 6MB).
- 使用
'ndk-build'命令:
'ndk-build'脚本位于NDK安装路径下的最上层目录,可以在你的应用工程目录或者它的子目录下直接调用,如:
cd $PROJECT
$NDK/ndk-build
这个命令会调用NDK编译脚本,自动检测你的开发环境和需要编译的应用工程文件。如:
ndk-build
ndk-build clean --> clean generated binaries
ndk-build -B V=1 --> force complete rebuild,showing commands
默认情况下,需要一个必须的$PROJECT/jni/Android.mk和一个可选的$PROJECT/jni/Application.mk。
执行成功后,会在你工程目录的适当位置下生成二进制modules(如共享库)。在这之后你就可以使用'ant'或者Eclipse插件来生成完整的Android应用包。
查阅docs/NDK-BUILD.html文档获取该脚本更加完整的介绍以及它可以使用的参数。
- 使用
$NDK/apps/<name>/Application.mk:
这个方法是Android NDK r4之前唯一的编译方法,并且为了兼容性保留了下来。强烈建议你尽可能使用'ndk-build'命令,因为我们有可能会在后续的NDK更新中移除对该方法的支持。
使用该命令有如下要求:
- 在你的
NDK安装目录(不是你的工程目录)下创建子目录$NDK/apps/<name>
这里的<name>可以任意但不允许有空格,主要用来向NDK编译环境描述你的应用。 - 编写
$NDK/apps/<name>/Application.mk,要求定义APP_PROJECT_PATH,支出你的应用工程的目录。 - 使用命令行进入到NDK安装目录,并在最上层执行
GNUMakefile,如:
cd $NDK
make APP=<name>
Rebuild your application package:
在使用NDK生成二进制库文件之后,你需要使用一般的方式重新打包你的Android应用文件(.apk),如'ant'或者Eclipse。
详情可查看Android SDK文档。新生成的.apk已经嵌入你的共享库了,而且会在你安装你的应用到设备时被系统自动的提取。
Debugging support:
NDK提供了一个名为'ndk-gdb'的帮助脚本,通过它可以很轻松的在你的应用程序中进行调试。
Native调试只能在Android 2.2以上的设备上进行,不需要root,也不需要特殊权限,只要你的应用是可以调试的即可。
更多内容可以查阅docs/NDK-GDB.html。简单地说,native调试遵循如下简单的步骤:
- 确保你的应用时可调试的(如:在
AndroidManifest.xml设置android:debuggable="true") - 使用
'ndk-build'编译你的应用,然后安装到你的设备或者模拟器上 - 打开你的应用
- 从你的应用目录下执行
'ndk-gdb'
你会看到一个
gdb提示,详细的指令请查看GDB用户帮助。