Android NDK Overview


前言:


Android NDK是一个工具集合,允许Android应用程序开发者嵌入由cc++编译的本地代码到你的应用程序包中。


重要提示:

 Android NDK只能用在Android 1.51.5以后的平台上。

 1.01.1系统不支持subleABI和工具链,因为这些在1.5版本才有。



IAndroid NDK 目标:

------------------------------


Android VM允许你的应用程序通过JNI调用本地实现的方法。简而言之,这意味着:


- 你的应用程序将会声明一个或者多个带有‘native’关键字的方法,而‘native关键字表明这些方法是通过本地代码来实现的。例如:


native  byte[] loadFile(String  filePath);


-你必须提供一个包含这些方法实现的本地共享库,这个共享库会被打包到你应用程序apk中。库的命名必须按照标准Unix命名方式,即lib<something>.so,并且必须包含一个标准的JNI入口点(稍后介绍),例如:


libFileLoader.so


-你的应用程序必须显示的加载这个库。例如,在程序启动的时候加载,只需要在你的源代码中添加以下代码:


static{

  System.loadLibrary(“FileLoader”);

}


记住这里你不要使用前缀‘lib’和后缀‘.so’。



Android NDKAndroid SDK中的一个组件,能够帮助你:


-生成能够运行在ARM CPUAndroid 1.5(以及1.5以上)平台的与JNI兼容的动态库。


-拷贝生成的动态库到你应用程序合适的目录下,这样动态库就能够自动的添加到你的最终apk中。


-在以后的NDK版本中,我们将会提供通过gdb远程连接的工具来帮助你调试本地代码,并尽可能多的提供 source/symbol 相关信息。



更进一步的,Android NDK提供以下内容:


-交叉工具集(编译器,链接器,等等),能够生成LinuxOS XWindows(通过Cygwin)系统上的本地ARM二进制文件。


-Android平台支持的一序列的比较稳定的本地api所对应的系统头文件。这种对应关系能够确保在以后的版本中都被支持。


它们被定义在STABLE-APIS中,请查看相关内容。


重要:

记住Android系统中的大部分本地库都不是固定不变的,在以后的版本中有可能发生剧烈的变化,甚至被删除。


-一个编译系统,允许开发者写一个非常简单的编译脚本来描述源代码是如何进行编译的。编译系统处理了toolchain/platform/CPU/ABI的所有细节。并且,NDK的后续版本可能会增加更多的工具链,平台,系统接口,而开发者的编译文件不需要做任何修改(稍后介绍更多内容)。



II.以下不是Android的目标:

---------------------------------------


NDK并不是一种生成运行在Android设备上本地代码的好方法。事实上,你的应用程序还是应该用java来编写,这样能够处理Android系统的事件以及相关生命周期,从而避免弹出“Application Not Responding”对话框。


但是,你可以用一个小的“application wrapper在本地代码中写一个复杂的应用,用来恰当的启动/关闭它。


JNI有一个深刻的理解是非常值得推荐的,因为环境中的很多操作需要开发者指定动作,它们可能在典型的本地代码中并不常见。这些包括:


- 不同通过本地指针直接访问VM对象中的内容。例如,在循环遍历中,你不能安全地得到一个指向String对象的16位字符数组的指针。

- 当在JNI之间调用,本地代码想要保持VM对象的句柄时,需要显示的镜像引用管理。


NDK仅仅提供了Android平台支持的本地APIs和库中非常有限的系统头文件。一个典型的Android系统镜像包含很多本地动态库,这些被认为是一个现实细节,有可能在更新和发布平台之间发生激烈的变化。


如果一个Android系统库没有被NDK头文件显示的支持,那么应用程序不应该依赖于它,它们可能会打破在各种设备上的系统更新。


可选的系统库将会逐步的添加到NDK稳定APIs集合中。


IIINDK开发实战:

---------------------------


以下是用Android NDK开发本地代码的主要步骤:


1/ 把你的本地代码放在$PROJECT/jni…


2/ 编写$PROJECT/jni/Android.mk文件告诉NDK编译系统如何编译。


3/ 可选的:编写$PROJECT/jni/Application.mk告诉NDK编译系统更多的编译细节。开始的时候你可能不需要Application.mk,但是这可以让您针对多个CPU或覆盖编译器/链接标志。(请查看Application.mk了解所有细节)


4/ 从你工程目录或者子目录运行“$NDK/ndk-build”编译本地代码。


如果成功的话,最后一步将会拷贝你应用需要的动态库到你的应用程序工程的根目录下。你需要通过通常的方法生成最终的apk



现在,了解更多一些细节:


III1/ 配置NDK

--------------------------


之前的版本需要运行‘build/host-setup.sh’脚本来配置NDK。这个步骤在NDK r4中完全去除了。


III2/ 放置cc++源文件:

-------------------------------------


把你的本地代码放在下面的目录中:


$PROJECT/jni/


$PROJECT对应你应用程序的工程目录。


你可以自由的组织‘jni’里面的内容,目录的名称和结构不会影响最终生成的应用包,所以你没有必要像应用程序的包名那样使用类似com.<mycompany>.<myproject>这样的唯一标示名称。


注意cc++源文件是支持的。默认NDK支持的c++文件的后缀是‘.cpp,但是其他的后缀也可以支持(请查看Android.mk相关内容)。


通过调整你的Android.mk文件,把源文件放在不同的地方也是可以的(see below)。



III3/ 编写Android.mk编译脚本:

-----------------------------------------------


Android.mk是一个很小的编译脚本,用来向NDK编译系统描述你的源文件。它的语法在ANDROID-MK有相信的描述。


简而言之,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 $(callall-subdir-makefiles)


这会包含当前编译文件目录下所有子目录中的Android.mk文件。



III4/ 编写Application.mk编译脚本(可选)

---------------------------------------------------------


Android.mk描述编译系统中的modules,而Application.mk描述应用程序本身。请查看APPLICATION-MK文档来了解这个文件可以做什么。这些包括:


-你的应用程序需要的确切的modules列表

-生成机器代码的CPU架构

-可选信息,类似release编译还是debug编译,是否指定了cc++编译器flag,还有其他用到所有modules编译的选项。


这个文件是可选的:NDK默认编译Android.mk(和所有包含的makefiles文件)列出的所有modules,并针对默认的CPU ABIarmeabi)。


有两者方式来使用Application.mk


- 把它放在$PROJECT/jni/Application.mk,它会被‘ndk-build’脚本自动找到(更多请看下面)。


- 放在$NDK/apps/<name>/Application.mk下,$NDK指向你NDK的安装路径。然后从NDK目录启动“nake APP=<name>”。


这个是Android NDK r4之前的使用方式。考虑到兼容性,依然支持它。但是强烈建议你使用第一种方法,因为它更简单,并且不需要修改/改变NDK安装树的目录。


再一次说明,请查看Application.mk文件了解它的完整描述。



III5/  调用NDK编译系统:

-----------------------------------------


NDK编译机器代码的最好方式是使用Android r4引入的‘ndk-build’脚本。你也可以使用一个过时的方法,这个方法要创建‘$NDK/apps’子目录。


在这两种情况下,编译成功会拷贝应用程序需要的最终的二进制文件modules(如动态库)到你应用程序工程目录下(注意unstripped versions会保持debug,因此没有必要拷贝unstripped binaries到设备中)。


1:使用‘ndk-build’命令

------------------------------------


ndk-build’脚本位于NDK安装路径的顶层目录中,它可以从你应用程序工程目录(即AndroidManifest.xml所在的地方)或任何子目录调用。例如:


cd $PROJECT

$NDK/ndk-build


启动这个编译脚本会自动检测到你的开发系统和决定怎么编译应用程序的工程文件。


例如:


ndk-build

ndk-build          clean            -->清楚生成的二进制文件

ndk-build          -B V=1         -->强制重新进行完整编译,显示命令


默认情况下,它需要一个可选的$PROJECT/jni/Application.mk文件和一个必须的$PROJECT/jni/Android.mk文件。


编译成功,会拷贝生成的二进制modules(如动态库)到你工程树中的适当位置。随后你可以用‘ant’命令或EclipseADT插件重新编译Andtoid项目。


请查看ndk-build相关部分,了解这个脚本的完整描述。


III6/ 指定自定义输出目录:

-----------------------------------------


默认情况下,ndk-build把所有的中间文件放在$PROJECT/obj 目录下。你可以定义NDK_OUT环境变量将它指向其他目录。


当它被定义的时候,这个变量会有两个作用:


      1/ 确保通常放在$PROJECT/obj目录下的文件全部保存在$NDK_OUT中。


2/ 告诉ndk-gdb$NDK_OUT中查找,而不是在$PROJECT/obj查找,生成的二进制文件的任何符号。


IV. 重新编译你的应用包:

-----------------------------------


在用NDK生成二进制文件后,你需要重新编译你的应用程序包(.apk),通过‘ant’命令或EclipseADT插件。


请查看Android SDK文档了解更多细节。当在设备上安装apk时,新的.apk会嵌入你的动态库,安装时会被系统自动的抽取出来。



VDebugging 支持:

----------------------------


NDK提供了一个帮助脚本叫‘ndk-gdb’,可以非常简单的为应用程序启动本地调试session


本地调试只能运行在Android2.2或更高的设备上,不需要root或特殊权限,只要你的应用是可调试的。


要了解更多信息,请阅读NDK-GDB相关内容。简而言之,本地调试的简单方案如下:


1.确保你的应用是可调试的(即在AndroidManifest.xml文件中设置android:debuggabletrue)。
2.用‘ndk-build’编译你的应用程序,然后安装到真机或模拟器。
3.启动你的应用程序。
4.从你应用程序工程目录运行‘ndk-gdb’。
你会看到一个gdb prompt。请查看GDB用户手册了解更多有用的命令。