Introduction:
Android NDK
是一个开发工具,可以帮助Android
应用开发人员嵌入由C
文件和(或)C++
文件编译生成的本地代码到第三方应用包中。
Android NDK
只能在Android 1.5
以上的平台(1.0
和1.1
系统不支持在释放1.5时对AB
I和工具链的重要变更)。
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
用户帮助。