Android NDK 功能概述
Android NDK就是一套用于把C/C++源码编译得到的二进制机器码嵌入应用安装包的工具。
Android NDK是对Android SDK的一个补充,可以帮助你:
1)生成符合JNI规范的共享库(运行在Android 1.5以上系统,主要是ARM CPU)
2)将共享库拷贝到工程合适位置(拷贝之后,在生成apk时,该共享库自动打包进最终的apk文件)。
3)将来的NDK版本,会提供调试本地码的工具(利用gdb连接和大量源码及符号信息)。
Android NDK提供了:
1)一套交叉编译工具链(编译器、链接器等),可在Linux, OS X和Windows(通过 Cygwin)系统上直接编译出ARM代码。
2)一套系统头文件。这些头文件对应Android平台的稳定API(见Android Stable APIs)。所谓稳定API,就是今后更高版本的Android系统都会支持的API。
3)一个编译系统。开发人员只需编写很短的编译脚本文件,描述哪些源码文件需要编译,如何编译。该编译系统能处理繁琐的工具链/平台/CPU/ABI 等细节。今后版本的NDK将支持更多工具链、平台、系统接口,而不用修改编译脚本。
注意事项:
1)Android NDK只能用于Cupcake以后(即Android 1.5 以后)。尤其是1.0和1.1版Android,NDK完全不支持(因为ABI和工具链有一些修改,导致了不兼容)。
2)Android系统除稳定API,还自带很多共享库,但大部分都是可变的,今后有可能做修改。使用非稳定的API,在系统更新之后,程序可能无法工作。
I. Android NDK的目标
Android 虚拟机支持通过JNI调用本地码实现的函数(本地码就是C/C++编写,在不同平台上编译得到的机器指令。对arm而言,就是arm指令;对x86而言,就是x86指令)。这意味着:
1)你在用java开发Android应用时,可以用native关键字,声明相应方法是在本地码中实现的。例如:
native byte[] loadFile(String filePath);
2)对于这些native方法,你必须提供一个共享库(*.so文件),包含这些方法的实现。这个共享库将来会打包进apk。这个共享库必须按照UNIX共享库的命名规则命名,例如:
lib<something>.so
它还必须包含一个标准的JNI入口函数。
3)java程序在使用这些native方法之前,必须加载该共享库。例如,下面代码是在启动的时候加载:
static {
System.loadLibrary("FileLoader"); // 这里应该取共享库文件名中间部分,不含“lib”前缀和“.so”后缀。
}
II. Android NDK不能完成的事情
用NDK开发一个一般化程序不是好方法,应该尽量用Java开发,处理各种Android系统事件和Android程序生命周期。
不过,你可以利用NDK来编写一个程序(大部分是C/C++),然后用一个小的启动器(Java)来加载。
对JNI有一程度的了解,能够帮助你更好地使用NDK。
NDK其实只提供了Android系统库的少数有限API,更多API因为可能修改所以没有在NDK中提供(因此找不到相应的头文件)。
III. NDK开发步骤
这里初步描述一下如何使用Android NDK进行开发:
1)将源码文件放在 $PROJECT/jni/...
2)编写 $PROJECT/jni/Android.mk 文件(编译脚本,供Android NDK编译系统使用)
3)可选:编写 $PROJECT/jni/Application.mk (供Android NDK编译系统使用。非必须。
可以用来支持更多的CPU平台,或用来修改编译器参数)。
4)进入工程根目录之后,运行ndk-build,完成编译。编译完成后,会自动拷贝strip过的共享库
到工程的根目录。然后,通过正常步骤来生成最终的apk文件。
上面步骤更详细的描述如下:
III.1 配置 NDK: 老版本NDK需要运行 build/host-setup.sh 脚本(配置NDK)。NDK r4之后(含r4),不需要该步骤。
III.2 拷贝C/C++文件到jni目录: 将 C/C++ 文件拷贝到 $PROJECT/jni/ 。该目录下的文件结构任意,不影响最终的apk。
C++代码的默认文件扩展名为cpp,其他扩展名也可以(见 android-mk )。 也可以把源码放到其他地方,只要在Android.mk中写明即可。
III.3 编写 Android.mk: 该文件有自己的语法(见android-mk)。NDK是把那些C/C++代码文件看做不同的“模块”,每个模块可以是一个静态库,也可以是一个动态库。在一个Android.mk文件中可以定义多个模块。也可以为每个模块写一个android.mk。
注意:同一个Android.mk可能被Android编译系统解析多次,所以要注意前面的环境变量对后面的影响。
默认情况下,NDK会寻找 $PROJECT/jni/Android.mk 文件。如果jni目录下还有子目录,可以在这些子目录中创建Android.mk,然后在最顶层的Android.mk文件中把这些Android.mk包含进来。有一个辅助函数(将把jni目录下所有的Android.mk包含进来):
include $(call all-subdir-makefiles)
III.4 编写 Application.mk(可选): Application.mk描述程序自身,而Android.mk描述模块如何编译。Application.mk提供很多功能,一些重要功能如下:
1)列出你的程序必需的模块 2)指定CPU架构 3)可选信息:例如想要release还是debug版本,C/C++编译器标志等全局性信息。
该文件是可选的,如果你没有提供,则编译系统用默认的Application.mk(将Android.mk文件中描述的模块全部编译,默认CPU为armeabi)
使用Application.mk的两种方法:
1)放在 $PROJECT/jni/Application.mk ,会被ndk-build脚本自动检测。
2)放在 $NDK/apps/<name>/Application.mk,进入NDK所在目录,执行 make APP=<name> 来完成编译。
以上第2)种方法用于NDK r4以前,现在依然支持(兼容)。 建议用方法1),因为它更简单,且不需要修改NDK安装目录。
III.5 调用NDK编译系统
调用的方式有2种:1)直接执行 ndk-build (更好) 2)在 $NDK/apps 下新建一个子目录的方法(老方法)
这两种方法编译成功后,将把strip过的二进制模块(即共享库)拷贝到工程目录(没有strip的模块也会保留,用于调试)。
1)用 ndk-build 命令
ndk-build脚本位于NDK的根目录,可以在进入工程目录之后调用(工程目录就是你的Android工程中,AndroidManifest.xml文件所在的目录)。
例如:
$cd /home/wuxiao/workspace/NotePad (NotePad 是用eclipse创建的)
$ndk-build
$ndk-build clean 相当于 make clean
$ndk-build -B V=1 -B 是强制重新编译, V=1 表示打印详细信息
编译出二进制代码后,按普通Android的app的步骤,生成apk。此时共享库已拷贝到工程的根目录,
所以这个共享库会进入apk。
2)老方法:用 $NDK/apps/<name>/Application.mk 方式(为了兼容而保留,即将被删除,此处省略)
IV. 编译生成apk
用NDK生成二进制模块后,按正常步骤编译生成apk(可用ant或ADT,在Android SDK文档中讲解)。
生成的apk包含该共享库。用户在安装该apk的时候,共享库会被系统自动提取。
V. 调试该共享库
NDK提供了一个ndk-gdb,运行后开启一个调试会话,连接到你的应用程序。
这种本地码调试只能在Android 2.2以上系统应用,不需要root权限或其他特权,只需要开启调试。
大致步骤如下(详细步骤见文档 NDK-BUILD):
1)在 AndroidManifest.xml 中设置调试功能为开启(将 android:debuggable 设置为 true)。
2)用 ndk-build 编译共享库,生成apk,安装apk
3)启动该程序
4)cd进入工程目录,执行 ndk-gdb,就进入了gdb命令行。