前两天被人问起是否了解Android下的NDK开发,当时懵了,之前确实听说过这么个工具集,但具体做什么用,怎么用,为什么用都一无所知。于是决定好好学习下。NDK目前最新版本是r4b,本人学习的资料主要是NDK-r4b自带的的docs/目录下的.txt文件。
Android NDK是个工具集,利用NDK可以实现在java应用中嵌入用C/C++编写的本地代码编译后的二进制码,二进制码以动态库的形式存在。不过,NDK只能用于1.5版本及其以后的系统。
开发NDK的目标有三个 :一是在应用中调用通过JNI实现的本地程序(C和C++编写);二是对SDK起到辅助作用;三是提供一些工具和平台说明文件。
实现NDK第一个目标有下面几件事要做:
1、在应用程序中,通常是JAVA编写的Class,用"native"关键字声明需要调用的本地程序。这里将C和C++编写的程序称为本地程序是相对于运行于虚拟机(VM)上的应用而言的。如: native byte[] loadFile(String filePath);
2、提供包含1中声明的本地程序的动态库,并将其打包到应用程序的.apk中。动态库的命名需要遵循UNIX的约定,采用lib<something>.so,包含JNI的入口点。如:libFileLoader.so
3、在应用程序中显示的加载2提供的动态库。通常在应用程序的起始位置添加如下代码:
static {
System.loadLibrary("FileLoader");
}
注意:这里引用动态库时,不需要lib前缀和.so后缀。
NDK对SDK的辅助作用体现在一下几点:
1、产生与JNI标准兼容的动态库,可以运行在ARM CPU+1.5版本及其以后的平台上。
2、编译生成并拷贝动态库到应用工程的合适位置,然后自动将其添加到.apk中。运行ndk-build,会自动完成编译和打包的工作。
3、目前没有提供本地代码调试工具,在以后的版本中考虑添加通过远程GDB连接调试的工具
NDK提供了一套工具和文件:
1、一套交叉编译工具,包括:编译器,连接器等等,可用来在Linux, OS X and Windows (通过 Cygwin)平台上产生ARM二进制文件。
2、一组与Android平台支持的稳定的本地API列表相对应的系统头文件,在文件docs/STABLE-APIS.TXT中有说明。这里的稳定API是指那些能够保证被当前及其之后版本都支持的API,因为,在Android的系统镜像中,大部分本地系统库并非一成不变的,可能变动很大甚至被删除在当前系统的后续版本中。
3、一个系统构建工具(build system),允许开发者编写短小的构建文件(android.mk/application.mk)来描述需要编译哪些源文件、怎样编译。NDK将在其后续版本中继续更新系统构建工具集、平台系统接口,而无须开发者修改他们的构建文件。
另外,NDK下还提供了一套Sample,可以通过阅读那里面的代码更直观的感受NDK的开发精髓。
作为NDK的使用者,除了掌握NDK能做什么之外,还有注意NDK作为一种工具和辅助手段,必有其局限性,如同高悬于顶上的德谟克利斯之剑 ,因此,开发者在使用时,也需要掌握一些使用规则:
1、不要用NDK去开发通用的运行在Android设备上的程序。尤其要强的是应用程序以及系统事件的处理程序应坚持用JAVA语言来编写,尽管利用NDK的本地编码方式也能编写复杂的应用,但不推荐这样做。根据本人的开发经验来看,这样做的代价实在太大,可能用JAVA几行的代码,用NDK的本地代码要几百行,这跟JNI的那套机制有关,需要通过一系列的手段在C/C++代码中去回调JAVA代码。
2、对JNI接口标准有良好的理解,因为该标准下要求开发者采取一些特殊的做法,而这些做法不是典型的本地代码的通行操作,如:
1}、本地代码无法直接访问VM(Virtual Machine)上对象的内容。
2}、当本地代码想在JNI调用中处理VM上的对象,自己就必须显示的进行引用管理。JNI标准支持LocalRef、GlobalRef以及WeakGlobalRef三类引用,需要本地代码对这些引用进行适当的处理,否则会带来内存泄露甚至程序崩溃的恶果。
3、NDK之提供了Android平台支持的数量非常有限的本地API的集的系统头文件和库文件,而典型的Android系统镜像包含了大量的其它本地动态库,这种做法的目的是考虑到NDK中未包含的库在Android后续版本中可能被大规模修改。因此,实际应用中,应用程序不应该依赖那些非NDK的库。
NDK开发步骤粗略概括为以下几步:
1、将本地代码放在路径:$PROJECT/jni/...,这里的$PROJECT是指当前JAVA应用工程的顶级目录。
2、为本地代码编写Android.mk文件,并置于$PROJECT/jni/...目录下,该文件是本地代码的构建描述文件
3、可选项:编写Application.mk,同样置于$PROJECT/jni/...目录下,该文件更详细的描述了工程的构建过程。尽管不是必须的,如果开发者这么做的话,就可以指定多个CPU平台,修改编译和连接标志。详情见docs/APPLICATION-MK.TXT
4、在$PROJECT/jni目录下执行$NDK/ndk-build命令,完成本地代码的构建。如果已经将NDK的安装路径$NDK添加到系统标准路径中了,则可直接运行ndk-build。如果构建成功的话,生成的动态库会被自动拷贝到当前工程目录下的lib文件夹中,并被添加到.apk中。
NDK开发步骤的详细描述如下:
1、进行NDK开发前,首先要进行NDK的配置。NDK-r4b之前版本的配置是直接运行build/host-setup.sh来完成的。而在该版本中已经将这过程移除,因此,在build目下已经没有host-setup.sh脚本了。真要将下载的NDK开发包直接解压即可,不过,为了方便使用,最好将解压目录添加到系统的标准路径中,这样就可以在系统中的任何路径下直接使用NDK命令了。
2、C/C++源文件的放置:直接至于目录$PROJECT/jni/下,如果jni目录不存在,新建之。jni下文件的组织是很自由的,目录的名称和结构不会影响应用程序生成的包,因此,开发者不需要像应用程序包里那样进行文件命令。C++文件默认的扩展名是.cpp,同时,也能处理其它类型的扩展名,详情见docs/ANDROID-MK.TXT。当然,通过调整Android.mk文件也可以将C/C++源文件放在其它地方。
3、Android.mk脚本编写:这是个小型的用于NDK开发的脚本,描述NDK中本地代码的构建。NDK将本地代码编译成一个或多个模块,这些模块可能是动态库或静态库。值得注意的是一个Android.mk可以定义多个模块的构建,也可以为每个模块单独编写一个Android.mk。如果是单个Android.mk对应多模块的话,Android.mk可能会被构建系统多次读取,因此,不要做某些变量在Android.mk中未被定义的假设。默认将Android.mk放在$PROJECT/jni目录下,如果想将其放在子目录下,就要在顶层目录的Android.mk中显示的指出来,如: include $(call all-subdir-makefiles),这样是告诉构建系统去遍历当前构建文件所在路径子目录下的所有Android.mk。
4、Application.mk的编写(可选):相对于描述模块构建的Android.mk而言,该文件是用来对应用程序本身构建进行描述的,描述内容通常包括:应用所需的所有模块的列表、目标CPU架构以及编译版本(Debug 或Release),c/c++编译标志及其它适用于所有模块的编译选项等可选内容。该文件是可选的,NDK默认的会提供一个简单的Android.mk,里面只是列举所有需要构建的模块,CPU架构默认的是ABI(armeabi)。Application.mk的使用有两种方式:
1}、直接放到$PROJECT/jni目录下,ndk-build脚本会自动读取
2)、将Application.mk放到$NDK/apps/<name>/目录下,$NDK是NDK的安装目录,在该目录下通过命令:make APP=<name> 来执行该脚本。这种方式是r4b版本之前的NDK常用的方式,为了保持兼容,r4b也支持这种用法,但不建议这么用,还是建议的一种方式,相对来说简单,不需要关心NDK的安装路径。
5、启动NDK构建系统,两种方法:
1)、使用ndk-build命令:ndk-build脚本位于$NDK下
2)、使用$NDK/apps/<name>/Application.mk。这种方式在r4b以前的版本中使用,虽然在r4b中也可以用,不过不推荐。因此,使用此法比较繁琐,需经以下步骤:
首先,在NDK的安装目录下创建子目录$NDK/apps/<name>/,name用来向NDK构建系统描述应用,不允许带有空格
其次,编写Application.mk,并置于$NDK/apps/<name>/下,在Application.mk中需要定义APP_PROJECT_PATH,用来指向工程目录$PROJECT
最后,切换到$NDK下,执行命令:make APP=<name>
NDK的调试支持:NDK中提供了ndk-gdb脚本,能很方比的启动应用程序中的本地调试会话。本地调试仅适用于Android2.2及其之后的版本,只要应用程序开启debuggable即可,不需要root权限以及其它特殊访问权限。更详细的信息见:docs/NDK-GDB.TXT。不过,本地调试的启动可以简单归纳为以下几步:
1、确定应用的debuggable已经启动,在AndroidManifest.xml中设置android:debuggable=true 即可;
2、利用ndk-build构建应用程序,并将其安装到设备/模拟器上;
3、在设备/模拟器上启动应用;
4、在应用工程目录$PROJECT下运行ndk-gdb
这样就会出现gdb命令行,参照gdb使用手册上的命令就可以进行调试了。