Android C/C++开发指南
1.Android C/C++简介
众所周知,Android作为目前主流的移动终端领域的开发平台,其主要的开发语言就是JAVA。Android借助于JAVA高效、灵活的开发模式,迅速占领了移动互联网开发的半壁江山。基于JDK和Android实现的各种JAVA框架,开发人员可以快速实现各种功能的APP,而且APP的安装和升级维护都实现的方便。
对于,AndroidJAVA的开发模式大家已经很熟悉了,今天要说的是基于Android 平台如何开发原生的C/C++应用程序程序。Google对于AndroidC/C++开发提供了NDK(Native Development Kit),基于NDK我们可以编译出能够在Android平台上运行了动态库/静态库/可执行程序。对于动态库/静态库,JAVA程序可以通过JNI(Java Native Interface)机制实现与其通信。
说到这里,可能大家觉得开发Android C/C++程序还需要花时间学习NDK的使用方式,未免有些麻烦。其实,基于NDK可以生成传统意义上的交叉开发工具链,对于经常开发嵌入式Linux应用程序的开发人员来说,对于交叉工具链的使用再熟悉不过了,所以对于不愿意使用NDK开发方式的程序员来说,使用交叉工具链则简单、高效的多,下面分别介绍这两种开发方式。
2.AndroidNDK开发方式
下面是官方对于NDK的简要介绍:NDK是一套可以允许APP调用像C或者C++编写的原生代码的工具集。对于某些类型的APP,NDK可以实现现有代码库的复用,但对于大多数的APP来说不需要NDK提供的这项服务。
官方文档同时说明,NDK对于大多数的APPs并没有太多的好处。并提醒,作为一名开发者,需要平衡使用NDK的利与弊。需要注意的是,在Android平台上使用原生的代码,一般不会带来可观的性能提升,但它常常增加APP的复杂度。所以,除非你的APP必须使用NDK,其他情况下,不要仅仅为了使用C/C++编程而使用NDK。
典型的使用NDK的场景是CPU密集型的应用,例如游戏引擎、信号处理、物理环境模拟等。当你使用NDK之前,请确定Android框架提供的APIs是否满足你的需求。
好了,上面就是官方对于NDK的简单介绍,具体使用场景大家可以参考上面提到的注意事项。下面就对NDK的具体使用方式做一下介绍。
2.1NDK开发环境搭建
首先,介绍介绍一下主机开发环境:
操作系统:Ubuntu14.04 TLS 32bit内存:4G |
2.1.1安装NDK
NDK的官方下载地址:wear.techbrood.com/tools/sdk/n…
NDK的安装十分的简单,将下载来的NDK,解压就可使用,例如:
$tar jxvf android-ndk32-r10b-linux-x86.tar.bz2 -C /to/NDK/path |
这样NDK就安装好了,你可以按照自己的习惯重新给NDK的开发目录命名。
2.1.2NDK环境变量设置
使用NDK之前需要将NDK的安装目录加入到PATH环境变量中,具体的PATH变量可以配置到~/.bashrc文件中,配置完成后,使用source命令使之生效。
$source ~/.bashrc |
2.2Android.mk
NDK依赖于Android.mk完成具体的编译过程,Android.mk类似于GNU Makefile文件,下面的举一个Android.mk简单的例子。
LOCAL_PATH := (call my−dir)x000D include (call my-dir)_x000D_ include (callmy−dir)x000D include (CLEAR_VARS)x000D LOCAL_MODULE := hello-jni_x000D_ LOCAL_SRC_FILES := hello-jni.c_x000D_ include $(BUILD_SHARED_LIBRARY) |
下面是对于上面Android.mk的解析:
① LOCAL_PATH:=$(call my-dir):每个Android.mk的开始位置必须定义LOCAL_PATH。其用于定义源文件的根目录。在本例子中,函数’my-dir’为构建系统提供的内建函数,其用于返回Android.mk所在当前目录。
② Include $(CLEAR_VARS):CLEAR_VARS为构建系统提供的变量,其指向一个GNU Makefile,用来重置除LOCAL_PATH之外的所有以LOCAL开头的变量(例如,LOCAL_MODULE、LOCAL_SRC_FILES等)。这是必须的,因为在GNU Makefile执行系统中所有的构建控制文件的变量都是全局的,为了不相互干扰,所以在Android.mk中必须调用CLAER_VARS重置以LOCAL开头的变量。
③ LOCAL_MODULE:=hello-jni:LOCAL_MODULE变量用来定义Android.mk所描述的目标模块名称。这个名字必须为唯一的,并且不能带有空格。构建系统会自动的为其添加适当的前缀和后缀。例如,动态库的模块名‘foo’为会被命名为’libfoo.so’;需要注意的是如果模块的名称为libfoo,构建系统不会在为其添加‘lib’前缀,同样最后生成’libfoo.so’。
④ LOCAL_SRC_FILES := hello-jni.c:LOCAL_SRC_FILES变量包含所有需要被编译进模块中的C/C++源文件。需要注意的是,你不需要对应的头文件定义出来,构建系统会自动的为你分析依赖关系。
⑤ Include $(BUILD_SHARED_LIBRARY):BUILD_SHARED
_LIBRARY变量为构建系统提供的内建GNU Makefile脚本,其会收集距其最近的‘include $(CLEAR_VARS)之后的所有LOCAL_X变量的信息,从而决定如何构建最终的目标文件。与其相对应的是BUILD_STATIC_LIBRARY,其用来生成静态库。
上面为一个最为简单的Android.mk的分析,下面具体分析一下Android.mk的具体细节。SOURCES
2.2.1Android.mk变量
以下是在 Android.mk中依赖或定义的变量列表,可以定义其他变量为自己使用,但是NDK编译系统保留下列变量名:
a. 以LOCAL_开头的名字(例如LOCAL_MODULE)
b. 以PRIVATE_, NDK_ 或 APP_开头的名字(内部使用)
c. 小写名字(内部使用,例如‘my-dir’)
如果为了方便在 Android.mk 中定义自己的变量,建议使用前缀MY_,例如下面的例子:
MY_SOURCES:=foo.c ifneq((MYCONFIGBAR),) MYSOURCES+=bar.c endif LOCALSRCFILES += (MY_CONFIG_BAR),) MY_SOURCES+=bar.c endif LOCAL_SRC_FILES += (MYCONFIGBAR),)MYSOURCES+=bar.cendifLOCALSRCFILES+= (MY_SOURCES) |
注意:‘:=’是赋值的意思;'+='是追加的意思;‘$’表示引用某变量的值。
2.2.2NDK內建变量
这些 GNU Make变量在你的 Android.mk 文件解析之前,就由编译系统定义好了。注意在某些情况下,NDK可能分析 Android.mk 几次,每一次某些变量的定义会有不同。
(1)CLEAR_VARS: 指向一个编译脚本,几乎所有未定义的 LOCAL_XXX 变量都在"Module-description"节中列出。必须在开始一个新模块之前包含这个脚本:include$(CLEAR_VARS),用于重置除LOCAL_PATH变量外的,所有LOCAL_XXX系列变量。
(2)BUILD_SHARED_LIBRARY: 指向编译脚本,根据所有的在 LOCAL_XXX 变量把列出的源代码文件编译成一个共享库。
注意,必须至少在包含这个文件之前定义 LOCAL_MODULE 和 LOCAL_SRC_FILES.
(3)BUILD_STATIC_LIBRARY: 一个 BUILD_SHARED_LIBRARY 变量用于编译一个静态库。静态库不会复制到的APK包中,但是可以用于编译共享库。
示例:include$(BUILD_STATIC_LIBRARY)
注意,这将会生成一个名为 lib$(LOCAL_MODULE).a 的文件
(4)TARGET_ARCH:目标 CPU平台的名字
(5)TARGET_PLATFORM: Android.mk 解析的时候,目标 Android 平台的名字.详情可考/development/ndk/docs/stable- apis.txt.
android-3 -> Official Android 1.5 system images
android-4 -> Official Android 1.6 system images
android-5 -> Official Android 2.0 system images
... ...
(6)TARGET_ARCH_ABI: 暂时只支持两个 value,armeabi 和 armeabi-v7a。。
(7)TARGET_ABI: 目标平台和 ABI 的组合
(8)BUILD_EXECUTABLE:编译成为一个可执行文件。
2.2.3NDK內建函数宏
GNU Make函数宏,必须通过使用'$(call )'来调用,返回值是文本化的信息。
(1)my-dir:返回当前 Android.mk 所在的目录的路径,相当于于 NDK 编译系统的顶层。这是有用的,在 Android.mk 文件的开头如此定义:
LOCAL_PATH:=$(callmy-dir)
(2)all-subdir-makefiles: 返回一个位于当前'my-dir'路径的子目录中的所有Android.mk的列表。 例如,某一子项目的目录层次如下:
src/foo/Android.msrc/foo/lib1/Android.mk src/foo/lib2/Android.mk |
如果src/foo/Android.mk包含:include $(call all-subdir-makefiles) 那么它就会自动包含 src/foo/lib1/Android.mk 和 src/foo/lib2/Android.mk。
这这项功能用于向编译系统提供深层次嵌套的代码目录层次项功能用于向编译系统提供深层次嵌套的代码目录层次。
注意,在默认情况下,NDK 将会只搜索在 src/*/Android.mk 中的文件。
(3)this-makefile: 返回当前Makefile 的路径(即这个函数调用的地方)。
(4)parent-makefile: 返回调用树中父 Makefile 路径。即包含当前Makefile的Makefile 路径。
(5)grand-parent-makefile:返回调用树中父Makefile的父Makefile的路径
2.2.4模块相关变量
下面的变量用于向编译系统描述你的模块。应该定义在'include (CLEARVARS)′和′include (CLEAR_VARS)'和'include (CLEARVARS)′和′include (BUILD_XXXXX)'之间。
$(CLEAR_VARS)是一个脚本,清除所有这些变量。
(1)LOCAL_PATH: 这个变量用于给出当前文件的路径。
必须在 Android.mk 的开头定义,可以这样使用:LOCAL_PATH:=(call my−dir) ,如当前目录下有个文件夹名称 src,则可以这样写 (call my-dir) ,如当前目录下有个文件夹名称 src,则可以这样写 (callmy−dir),如当前目录下有个文件夹名称src,则可以这样写 (call src),那么就会得到 src 目录的完整路径 。这个变量不会被(CLEARVARS)清除,因此每个Android.mk只需要定义一次(即使在一个文件中定义了几个模块的情况下)。
(2)LOCALMODULE:这是模块的名字,它必须是唯一的,而且不能包含空格。必须在包含任一的(BUILD_XXXX)脚本之前定义它。模块的名字决定了生成文件的名字。
(3)LOCAL_SRC_FILES: 这是要编译的源代码文件列表。
只要列出要传递给编译器的文件,因为编译系统自动计算依赖。注意源代码文件名称都是相对于 LOCAL_PATH的,你可以使用路径部分,例如:
LOCAL_SRC_FILES:=foo.c toto/bar.c\
Hello.c 文件之间可以用空格或Tab键进行分割,换行请用""
如果是追加源代码文件的话,请用LOCAL_SRC_FILES += 。
注意:可以LOCAL_SRC_FILES := $(call all-subdir-java-files)这种形式来包含local_path目录下的所有java文件。
(4)LOCAL_C_INCLUDES: 可选变量,表示头文件的搜索路径。
默认的头文件的搜索路径是LOCAL_PATH目录。
(5)LOCAL_STATIC_LIBRARIES: 表示该模块需要使用哪些静态库,以便在编译时进行链接。
(6)LOCAL_SHARED_LIBRARIES: 表示模块在运行时要依赖的共享库(动态库),在链接时就需要,以便在生成文件时嵌入其相应的信息。
(7)LOCAL_LDLIBS: 编译模块时要使用的附加的链接器选项。这对于使用‘-l’前缀传递指定库的名字是有用的。
例如,LOCAL_LDLIBS := -lz表示告诉链接器生成的模块要在加载时刻链接
到/system/lib/libz.so 。可查看 docs/STABLE-APIS.TXT 获取使用 NDK发行版能链接到的开放的系统库列表。
(8)LOCAL_CFLAGS:用于指定C/C++编译器所使用的编译选项。
注意:不要试图通过LOCAL_CFLAGS修改Android.mk的优化/调试等级,Application.mk中定义的信息会自动控制,并让NDK产生有意义的调试信息。
可以通过如下设置完成额外的头文件的指定:
LOCAL_CFLAGS += -I |
但使用最好使用LOCAL_C_INCLUDES,因为ndk-gdb同样会使用这些路径。
更多的Android.mk信息请参看NDKdocs中的Android.mk
2.3 Application.mk
Application.mk目的是描述在你的应用程序中所需要的模块(即静态库或动态库)。
Application.mk文件通常被放置在 PROJECT/jni/Application.mk下,PROJECT/jni/Application.mk下,PROJECT/jni/Application.mk下,PROJECT指的是您的项目。
要将C\C++代码编译为SO文件,光有Android.mk文件还不行,还需要一个Application.mk文件。
下面具体描述Application.mk的语法使用。
1. APP_PROJECT_PATH :这个变量是强制性的,并且会给出应用程序工程的根目录的一个绝对路径。这是用来复制或者安装一个没有任何版本限制的JNI库,从而给APK生成工具一个详细的路径。
2. APP_MODULES :这个变量是可选的,如果没有定义,这个模块名字被定义在Android.mk文件中的 LOCAL_MODULE 中。 NDK将由在Android.mk中声明的默认的模块编译,并且包含所有的子文件(makefile文), NDK会自动计算模块的依赖。如果APP_MODULES定义了,它必须是一个空格分隔的模块列表( 注意:NDK在R4开始改变了这个变量的行为,在此之前: 在Application.mk中,该变量是强制的必须明确列出所有需要的模块)
3. APP_OPTIM:这个变量是可选的,用来定义“release”或"debug"。在编译您的应用程序模块的时候,可以用来改变优先级。 “release”模式是默认的,并且会生成高度优化的二进制代码。"debug"模式生成的是未优化的二进制代码,但可以检测出很多的BUG,可以用于调试。注意:如果你的应用程序是可调试的(即,如果你的清单文件在它的标签中把android:debuggable属性设为true),
默认将是debug而非release。把APP_OPTIM设置为release可以覆写它。注意:可以调试release和debug版二进制,但release版构建倾向于在调试会话中提供较少信息:一些变量被优化并且不能被检测,码重新排序可能致使代码步进变得困难,堆栈跟踪可能不可靠,等等。
4. APP_CFLAGS:一个C编译器开关集合,在编译任意模块的任意C或C++源代码时传递。它可以用于改变一个给定的应用程序需要依赖的模块的构建,而不是修改它自身的Android.mk文件
5. APP_BUILD_SCRIPT:默认,NDK构建系统将在 (APPPROJECTPATH)/jni 下寻找一个名为 Android.mk 的文件。即,对于这个文件(APP_PROJECT_PATH)/jni 下寻找一个名为 Android.mk 的文件。即,对于这个文件(APPPROJECTPATH)/jni下寻找一个名为Android.mk的文件。即,对于这个文件(APP_PROJECT_PATH)/jni/Android.mk如果你想重载这个行为,你可以定义APP_BUILD_SCRIPT指向一个不同的构建脚本。一个非绝对路径将总是被解析为相对于NDK顶级目录的路径。
6. APP_ABI : 默认情况下,NDK的编译系统根据 "armeabi" ABI生成机器代码。可以使用APP_ABI 来选择一个不同的ABI。比如:为了在ARMv7的设备上支持硬件FPU指令。可以使用 APP_ABI := armeabi-v7a或者为了支持IA-32指令集,可以使用 APP_ABI := x86或者为了同时支持这三种,可以使用 APP_ABI := armeabi armeabi-v7a x86
7. APP_STL :默认,NDK构建系统提供由Android系统给出的最小C++运行时库(/system/lib/libstdc++.so)的C++头文件。 然而,NDK带有另一个C++实现,你可以在你自己的应用程序中使用或链接它。定义APP_STL以选择它们其中的一个:
APP_STL := stlport_static --> static STLport library
APP_STL := stlport_shared --> shared STLport library
APP_STL := system --> default C++ runtime library
更多的Application.mk信息请参看NDKdocs中的Application.mk
2.4C/C++库编写
上面将NDK的安装、配置以及Android.mk和Application.mk的编写格式都进行了一些介绍,下面举几个简单的基于NDK的程序编写例子。本节介绍C/C++动态库和静态库的编写方式。下面是程序的源代码,很简单只打印“Hello, NDK!”。
首先,建立NDK的工作目录,例如:
$mkdir -p ~/android_ndk ~/android_ndk/jni |
打开~/.bashrc文件,添加NDK_PROJECT_PATH环境变量,如下:
export NDK_PROJECT_PATH=~/android_ndk |
并执行sourcr ~/.bashrc,是刚才配置的环境变量生效。
进入android_ndk目录,创建hello_ndk.c文件,代码如下:
#include <stdio.h>int hello_ndk(void){ printf("Hello, NDK\n"); return 0;} |
进入~/andorid_ndk目录,编写Android.mk,配置如下:
LOCAL_PATH := (call my−dir) include (call my-dir) include (callmy−dir) include (CLEAR_VARS) LOCAL_MODULE = hello LOCAL_SRC_FILES = hello.c include (BUILD_SHARED_LIBRARY)#编译libhello.so动态库#include (BUILD_STATIC_LIBRARY)#编译静态库libhello.a |
进入~/android_ndk/jni目录,编写Application.mk,配置如下:
APP_BUILD_SCRIPT= |
进入~/android_ndk目录,执行ndk-build,创建libhello.so动态库,如下:
[armeabi] Compile thumb : hello <= hello.c[armeabi] SharedLibrary : libhello.so[armeabi] Install : libhello.so => libs/armeabi/libhello.so |
2.5C/C++可执行程序编写
NDK同样可以编译能够在Android系统运行的可执行程序,将该程序通过adb置于android系统中,就可以直接运行它。生成可执行的程序的Androd.mk,如下:
LOCAL_PATH := (call my−dir) include (call my-dir) include (callmy−dir) include (CLEAR_VARS) LOCAL_MODULE = hell_ndk LOCAL_CFLAGS=-pie -fPIE#生成位置无关的可执行程序 LOCAL_SRC_FILES = main.c include $(BUILD_EXECUTABLE)#生成可执行程序 |
[armeabi] Compile thumb : hello <= hello.c[armeabi] Compile thumb : hello <= main.c[armeabi] Executable : hello[armeabi] Install : hello => libs/armeabi/hello |
有时,我们在编译可执行程序时,需要链接外部的动态库,那Android.mk该如何编写呢?根据Android.mk一节,我们需要用到两个NDK变量:LOCAL_SHARED_LIBRARIES和LOCAL_LDLIBS。
例如,我们已经生成了libhello.so,如果另一个文件main.c需要调用hello_ndk函数,我们就需要调用libhello.so库,main.c如下:
#include "hello.h"int main(void){ hello_ndk(); return 0;} |
Android.mk如下:
LOCAL_PATH := (call my−dir) include (call my-dir) include (callmy−dir) include (CLEAR_VARS) LOCAL_MODULE = hello_ndk LOCAL_SHARED_LIBRARIES=libhello LOCAL_LDLIBS=-L./ -lhello #libhello.so动态库,注意必须添加-L制定动态库的位置 LOCAL_CFLAGS=-pie -fPIE LOCAL_SRC_FILES = main.c include $(BUILD_EXECUTABLE) |
3.Android Standalone Toolchain
如果开发者已经习惯了使用交叉工具完成应用程序的编译工作,NDK提供了了Standalone Toolchian方式,我们可以直接通过NDK生成交叉工具链,然后直接修改Makfile中的CC变量就可以直接生成可以在Android系统上运行的应用程序。
3.1制作StandaloneToolchain
首先,进入NDK的安装目录,进入build/tools目录,然后可以看make-standalone-toolchain.sh即为StandaloneToolchain的生成工具。
可以运行如下命令,查看其使用方法:
$./make-standalone-toolchain.sh --help --help Print this help. --verbose Enable verbose mode. --toolchain= 定义交叉工具链名称 --llvm-version= Specify LLVM version --stl= Specify C++ STL [gnustl] --arch=name> 指定目标平台例如:arm、x86、mips --abis= 指定所使用的ABI --ndk-dir= 制定NDK的安装目录 --system= Specify host system [linux-x86] --package-dir= 交叉工具链压缩包的存放位置 --install-dir= Don't create package, install files to instead. --platform= 指定Android平台的APIS,具体参考NDK/docs中的NDK Programmer's Guide中的Stable APIs的对应表。 |
下面为一个具体配置方式:
$./make-standalone-toolchain.sh --toolchain=arm-linux-androideabi-4.8 --ndk-dir=/home/lhl/android_ndk/android-ndk-r10b/ --package-dir=/tmp/ndk-4.8 --platform=android-19 |
完成后会在/tmp/ndk-4.8目录下生成arm-linux-androideabi-4.8交叉工具链的压缩包,arm-linux-androideabi-4.8.tar.bz2。将其到任意目录配置PATH环境变量后就可以想普通的交叉工具链一样使用了。
附录
NDK提供的开发工具
libc (C library) headerslibm (math library) headersJNinterface headerslibz (Zlib compression) headersliblog (Android logging) headerOpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headerslibjnigraphics (Pixel buffer access) header (for Android 2.2 and above).A Minimal set of headers for C++ supportOpenSL ES native audio librariesAndroid native application APIS |
NOTE:对于posixpthread的使用我们没有必要在LOCAL_LIBS中指定-lpthread,libc中已经包含了pthread的实现。同样对于rt(real-time extentions (-rt),同样已经包含进了libc库。