在 Android NDK 开发中,有两个重要的文件: Android.mk 和 Application.mk ,各尽其责,指导编译器如何编译程序,并决定编译结果是什么。本文将详细说明几个常见的 NDK 选项的配置,帮助大家理解相应的配置选项。
V2EX格式编辑起来比较累,大家可以查看原文链接 http://crash.163.com/#news/!newsId=24
一、 Application.mk
Application.mk 实际上是轻量级 Makefile ,通常在$PROJECT/jni 目录下,用于配置所有 modules 的编译变量,例子如下:
APP_ABI := armeabi arm64-v8a x86_64 x86 armeabi-v7a NDK_TOOLCHAIN_VERSION := clang3.5 APP_STL := stlport_static APP_OPTIM := debuge
1 、 APP_ABI (目标平台 ABI 类型)
NDK 编译中, APP_ABI 默认选择 armeabi ABI ,可通过设置 APP_ABI 设置一个或者多个 ABI ,表一为不同的 APP_ABI 所对应的指令集。
Instrunction set Value ARMv5TE based CPU APP_ABI := armeabi ARMv7 based CPU APP_ABI := armeabi-v7a ARMv8 AArch64 APP_ABI := arm64-v8a IA-32 APP_ABI := x86 Intel64 APP_ABI := x86_64 MIPS32 APP_ABI := mips MIPS64(r6) APP_ABI := mips64 All supported instruction sets APP_ABI := all 表一: ABI 类型
在开发时可根据需求选择 APP_ABI ,对于 ABI 的选择需要考虑到效率和 APK 大小。由于 armeabi-v7a 指令集兼容 armeabi ;市面上的 x86 手机为了兼容性,基本都使用 libhoudini 模块,兼容 arm 指令集; 64 位机型默认支持 32 位 abi 的 so ,因此在对大小要求比较高的情况下,可以只选择市面上设备基本兼容的 armeabi ABI ,如果对性能有些许要求,可以再添加 x86 ABI 。
2 、 NDK_TOOLCHAIN_VERSION (编译器类型、版本)
默认采用的是 GCC 编译器,对于 GCC 版本的选择与 NDK 版本有关系,本人使用的是 NDK R12 ,在 64 位 ABI 默认是 GCC 4.9 , 32 位 ABI 默认是 GCC 4.8 ,当然也可以像上面例子中给出的设置一样,设置 clang 编译器。
3 、 APP_STL (运行库类型)
Android NDK 默认使用的是最小支持的 C++运行库,如果你需要你的 NDK 程序中使用 STL ,则可以设置 APP_STL := stlport_static , APP_STL 有表二中的几种取值。
Name Explanation system(default) 系统默认的 C++运行库 stlport_static 以静态链接方式使用的 sttport 版本的 STL stlport_shared 以动态链接方式使用的 sttport 版本的 STL gnustl_static 以静态链接方式使用的 gnustl 版本的 STL gnustl_shared 以动态链接方式使用的 gnustl 版本的 STL gabi++_static 以静态链接方式使用的 gabi++ gabi++_shared 以动态链接方式使用的 gabi++ c++_static 以静态链接方式使用的 LLVM libc++ c++_shared 以动态链接方式使用的 LLVM libc++表二: NDK 运行库
若 APK 中有多个 SO 文件用到 STL ,建议都使用动态方式链接 STL ,这样可以减小整个 APK 文件大小。
另外需要注意的是官方提供的 NDK 运行库除了默认的以外都支持 RTTI 和异常,然而默认是禁用的,将在下面的 Android.mk 中说明如何开启。
4 、 APP_OPTIM (编译模式)
“ release ”模式为默认的,生成的是优化后的二进制;也可以设置为“ debug ”模式,“ debug ”模式生成的是未优化二进制,提供很多 BUG 信息,便于调试和分析。
还有其他配置选项,有兴趣可以查看 Application.mk 官方文档。
二、 Android.mk
Android.mk 也是一个轻量级的 Makefile ,其将 C/C++源码组织到一个个 module 中, module 可以是静态库、共享库或者独立的可执行文件, 一个 Android.mk 文件可以有一个,也可以是多个 module , modules 之间也可以有依赖关系。
1 、基本概念
Android.mk 中包括 NDK 提供的宏、变量以及模块描述变量,这些宏、变量以及变量的赋值共同组成了 Android.mk 文件,其在 NDK 编译中各尽其责,指导着 NDK 的编译。
宏:包括 my-dir 、 all-subdir-makefiles 等,通过‘$(call <function>)’来调用,返回文本信息。
变量:包括 CLEAR_VARS 、 BUILD_SHARED_LIBRARY 、 TARGET_ARCH 等,由 NDK 编译系统提供,并且在 Android.mk 文件被解析前就已经存在。 Android.mk 文件有可能被多次解析,因此每次解析时这些变量的值都有可能不同。
模块描述变量: Module-description ,包括 LOCAL_PATH 、 LOCAL_MODULE 、 LOCAL_SRC_FILES 等 LOCAL_前缀变量,这些变量除 LOCAL_PATH 外,均填写在语句 include $(CLEAR_VARS)和 include $(BUILD_XXX)之间。
其他 Android.mk 配置可以查看 Android.mk 官方文档。
2 、基础
在 Android.mk 中包括一些很基础的变量,下面的栗子包括了基础的变量,本人将详细说明。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH (当前目录)
LOCAL_PATH 为模块描述变量,一个 Android.mk 必须定义 LOCAL_PATH ,用于定位源文件,在本例中,使用的是编译系统提供的宏“ my-dir ”(“ my-dir ”返回最近一次包括 Makefile 文件路径,通常为当前 Android.mk 所在目录),用于返回当前目录。
此变量不会被 CLEAR_VARS 清除,所以每个 Android.mk 文件只需要定义一次就可以了。
CLEAR_VARS (变量清除)
CLEAR_VARS 变量由编译系统提供,顾名思义,作用是清除模块变量(在 include $(CLEAR_VARS)和 include $(BUILD_XXX)之间的 LOCAL_XXX 模块变量),当然 LOCAL_PATH 除外。由于所有的编译控制文件都是单一的 GNU Make 可执行上下文环境中解析,而这个上下文环境中所有的变量都是全局的,所以编译 module 前需要清理相应变量。
LOCAL_MODULE ( module 名称)
LOCAL_MODULE 是 Android.mk 文件中 module 的唯一标识,这个名字必须是唯一的,且中间不能有空格。在默认情况下,它决定了生成的文件名,如“ hello-jni ”对应的动态库名称为 libhello-jni.so ,然而要索引它时,需要“ hello-jni ”即可,也可以通过变量 LOCAL_MODULE_FILENAME 来覆盖这个默认名称。
LOCAL_SRC_FILES (源码文件)
LOCAL_SRC_FILES 变量包括 C/C++源文件列表,这些源文件会被编译到一个 module 中,不过也不必列出头文件和包括文件,编译系统会自动为你找打所有需要的依赖关系。值得注意的是 linux 下路径使用顺斜杠(/)。
BUILD_SHARED_LIBRARY (动态库编译)
BUILD_SHARED_LIBRARY 是编译器提供的变量,表示编译成动态库,它指向一个 GNU Makefile 脚本,这个脚本收集从 include $(CLEAR_VARS)后所有的 LOCAL_XXX 变量中定义的所有信息,决定编译什么以及怎么编译。
还有 BUILD_STATIC_LIBRARY ,和 BUILD_SHARED_LIBRARY 类似,表示编译成静态库,静态库不会被拷贝到 APK 中。
PREBUILT_SHARED_LIBRARY (预编译)
指向一个编译脚本,用来指定一个预编译动态库.使用此变量时,不像 BUILD_SHARED_LIBRARY 和 BUILD_STATIC_LIBRARY 那样, LOCAL_SRC_FILES 的值必须是只能有一个指向预编译动态库的路径,如 foo/libfoo.so ,而不是源文件。如下栗子。
include $(CLEAR_VARS) LOCAL_MODULE := test LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libtest.so include $(PREBUILT_SHARED_LIBRARY)
PREBUILD_STATIC_LIBRARY 和 PREBUILD_SHARED_LIBRARY 一样,只不过是用于引用静态库。
TARGET_ARCH_ABI (目标 ABI 名称)
如表一所示,目标 ABI 名称。若定义了多个 ABI ,则每次解析 Android.mk 时,值都不一样,主要使用场景为根本不同的 ABI 定义不同的文件等。
3 、其他模块变量 LOCAL_LDLIBS (链接库)
用于额外链接选项,所有的库都有“-l ”前缀。可同时列出多个库,用空格隔开,例如:
LOCAL_LDLIBS := -llog -ldl
Android NDK 默认链接了多个库,不需要显示的添加到 LOCAL_LDLIBS 中,包括 the standard C libraries , the standard C++ libraries , real-time extensions 和 pthread 库。同时也提供了一些需要显示添加的库,这些库版本有关系,如表三所示。
Android level Lib Explanation
Android-3 -llog Android Log -lz Zlib Compression Library -ldl Dynamic Linker Library Android-4 -lGLESv1_CM OpenGL ES 1.x Library Android-5 -lGLESv2 OpenGL ES 2.0 Library Android-8 -ljnigraphics The jnigraphics Library
Android-9 -lEGL The EGL graphics library -lOpenSLES Open ES native audio Library -landroid Natice Android API Android-14 -lOpenMAXAL OpenMAX AL natice multimedia Library Android-18 -lGLESv3 OpenGL ES 3.0 Library Android-21 -lGLESv3 OpenGL ES 3.1 Library 表三:链接库
LOCAL_CFLAGS 、 LOCAL_CPPFLAGS 和 LOCAL_LDFLAGS (编译、链接标志)
LOCAL_CFLAGS 定义的是在编译 C/C++时,传递给编译器的标志集合, LOCAL_CPPFLAGS 只支持 C++,作用也是传递给编译器一些信息, LOCAL_LDFLAGS 是指传递给连接器一些额外的参数。
在 NDK 开发中难免会用到这些标志位,特别是在优化编译时,下面的是本人在开发中遇到的编译选项。
① LOCAL_CPPFLAGS += -fexceptions
由于 NDK 编译从 R5 开始才支持 C++异常控制,为了通用性,异常处理默认是禁用的(-fno-exceptions ),因此需要在指定 module 中添加 LOCAL_CPPFLAGS += -fexceptions 编译选项方可编译带异常处理的 C++代码。也可以直接在 Application.mk 中配置 APP_CPPFLAGS += -fexceptions 。
② LOCAL_CPPFLAGS += -frtti
从 NDK R5 开始, NDK 也开始支持 C++ RTTI 了,但为了通用性,所有的 C++源文件被构建的时候默认是不支持 RTTI 的(-fno-rtti ),可以通过在 Android.mk 中添加: LOCAL_CPPFLAGS += -frtti 或者在 Application.mk 添加 APP_CPPFLAGS += -frtti 来开启 RTTI 。
③ LOCAL_CFLAGS += -fvisibility=hidden
在 NDK 开发中,源文件的函数都有一个默认的 visibility 属性为 public ,编译生成的 so 文件中几乎所有的函数名、全局变量名均被导出,其实只需要导出 java_com 开头的 jni 函数即可,其他函数不需要暴露出来,在 Android.mk 中设置 LOCAL_CFLAGS += -fvisibility=hidden ,就可以隐藏不需要导出的函数,若某个函数需要导出,则添加 JNIEXPORT 或者__attribute__ ((visibility ("default")))即可。
除了安全,不导出不必要的函数外,还能减小 so 体积。
④ LOCAL_CFLAGS += -ffunction-sections
不添加此参数时,编译文件.o 中代码部分只有.text 段,使用此参数,会使每个函数单独有一个段,举个栗子,函数 func1()会编译成.text.func1 段,虽然段多了,但对链接后代码大小并没有影响。
⑤ LOCAL_CFLAGS += -fdata-sections
同上,每个 data 都有一个单独的段。
⑥ LOCAL_LDFLAGS += -Wl --gc-sections
-Wl,<option>选项是告诉编译器,将后面选项<option>传递给连接器,-Wl,--gc-sections 的意思是使用连接器 ld 链接时删除不用的段。若使用 LOCAL_CFLAGS += -ffunction-sections -fdata-sections ,则代码和数据均被分割成不同的段,若某个函数或数据未被任何函数调用,则 ld 不会链接未被调用的函数,从而减小 so 文件体积,达到优化 so 的目的。
⑦ LOCAL_LDFLAGS += -fPIC
PIC(position independent code)用于编译位置无关代码,生成可用于共享库的位置独立代码。若不添加-fPIC ,则加载.so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段内容,这样就导致没使用这个.so ,代码段的进程在内核中就会生成这个文件的拷贝。
⑧ LOCAL_LDFLAGS += -Wall
这个的意思是 wring all 意思在编译和链接过程中显示所有警告信息。
⑨其他
若需要了解其他编译标志,可以查看 GCC Command Options 文档。
1
allenx 2016-08-16 18:18:20 +08:00
不错的 hello world
|
2
psklf 2016-08-16 19:34:02 +08:00
写的不错,支持,我的建议:
1. 贴文章的时候稍微调一下格式, md 不难吧。 2. 希望后续能介绍使用 Android Studio gradle 编译运行 ndk 的方法。 |
3
FrankHB 2016-08-16 19:47:54 +08:00
|
4
ilotuo 2016-08-16 20:04:37 +08:00 via Android
放到博客再分享吧
|
5
pangliang 2016-08-17 09:39:18 +08:00
新人的话, 不建议学老的编译方式了.... 最新版的 as 的 gradle 编译已经很好用了
|
6
cxl008 2016-08-17 14:18:13 +08:00
排版再好点就更好了
|
7
nashge 2016-08-17 16:08:36 +08:00
不错的 hello world
|
8
wiket 2017-09-08 10:17:27 +08:00
现在基本都用 gradle+cmake 了吧
|