1 概念
ndk-build 本质上是一个脚本,它的位置就在 NDK 目录的最上层,即在< NDK >/ndk-build 路径下。运行 ndk-build 脚本相当于运行以下命令:
$GNUMAKE -f <ndk>/build/core/build-local.mk
<parameters>
$GNUMAKE 指向 GNU Make 3.81 或更高版本, 则指向 NDK 安装目录。
官方文档链接
2 组成
ndk-build 脚本使用 NDK 的基于 Make 的构建系统构建项目。使用 ndk-build我们需要两个配置文件: Android.mk 和 Application.mk。
2.1 Android.mk
Android.mk 更像是一个传统的 makefile,定义源代码、包含头文件的路径、链接器的路径来定位库、模块名、构建类型等等。
- LOCAL_PATH :=$(call my-dir)
返回当前文件在系统中路径,Android.mk 文件开始时必须定义该变量。 - include $(CLEAR_VARS)
表明清楚上一次构建过程的所有全局变量,因为在一个 Makefile 编译脚本中,会使用大量的全局变量,使用这行脚本表明需要清除掉所有的全局变量。 - LOCAL_SRC_FILES
要编译的 C 或者 CPP 的文件,注意这里不需要列举头文件,构建系统会自动帮组开发者依赖这些文件。 - LOCAL_LDLIBS:=-L$ (SYSROOT)/usr/lib -llog -lOPENSLES -lGLESv2 -lEGL -lz
指定编译过程所依赖的 NDK 提供的动态和静态库,SYSROOT变量代表的是 NDK_ROOT 下面的目录 $NDK_ROOT/platforms/android-18/arch-arm,而在这个目录的 usr/lib/ 目录下有很多对应的 .so 的动态库以及 .a 的静态库。 - LOCAL_CFLAGS
编译 C 或者 CPP 的编译标志,在实际编译的时候会发送给编译器。比如常用的实例是加上 -DAUTO_TEST , 然后在代码中就可以利用条件判断 #ifdef AUTO_TEST 来做一些与自动化测试相关的事情。 - LOCAL_LDFLAGS
链接标志的可选列表,当对目标文件进行链接以生成输出文件的时候,将这些标志带给链接器。该指令与 LOCAL_LDLIBS 有些类似,一般情况下,该选项会用于指定第三方编译的静态库,LOCAL_LDLIBS 经常用于指定系统的库(比如 log、OpenGLES、OpenSLES 等)。 - LOCAL_MODULE
该模块的编译的目标名,用于区分各个模块,名字必须是唯一并不包含空格的,如果编译目标是 so 库,那么该 so 库的名称就是 lib 项目名 .so。 - include $(BUILD_SHARED_LIBRARY)
其实类似的 include 还有很多,都是构建系统提供的内置变量,该变量的意义是构建动态库,其他的内置变量还包括如下几种。- BUILD_STATIC_LIBRARY: 构建静态库
- PREBUILT_STATIC_LIBRARY: 对已有的静态库进行包装,使其成为一个模块。
- PREBUILT_SHARED_LIBRARY: 对已有的静态库进行包装,使其成为一个模块。
- BUILD_EXECUTABLE: 构建可执行文件。
官方文档链接
2.2 Application.mk
Application.mk 定义了 Android 应用程序相关的属性,如 Android SDK 版本、调试或发布模式、目标平台 ABI (架构二进制接口)、标准 c/c++ 库等。
- APP_ABI := XXX,这里的 XXX 是指不同平台,可以选填的有 x86 、X86_64 、armeabi-v8a、armeabi-v7a、all 等,值得一提的是,若选择 all 则会构建构建出所有平台的 so,如果不填写该项,那么将默认构建为 armeabi 平台下的库。
- APP_STL := gnustl_static,NDK 构建系统提供了由 Android 系统给出的最小 C++ 运行时库 (system/lib/libstdc++.so)的 C++ 头文件。
- APP_CPPFLAGS :=-std=gnu++11 -fexceptions,指定编译过程的 flag ,可以在该选项中开启 exception rtti 等特性,但是为了效率考虑,最好关闭 rtti。
- NDK_TOOLCHAIN_VERSION = 4.8,指定交叉工具编译链里面的版本号,这里指定使用 4.8。
- APP_PLATFORM :=android-21,指定创建的动态库的平台
- APP_OPTIM := release,该变量是可选的,用来定义 “release” 或者 “debug” ,“release” 模式是默认的,并且会生成高度优化的二进制代码;“debug” 模式生成的是未优化的二进制代码,但是可以检测出很多的 BUG,经常用于调试阶段,也相当于在 ndk-build 指令后边直接加上参数 NDK_DEBUG=1。
官方文档链接
3 ndk-build 转换为 CMake
在Android Studio 2.2 之后,工具中增加了 CMake 的支持,所以在 Android Studio 2.2 之后有2种方式来编译 c/c++ 代码。
- 一种是 ndk-build + Android.mk + Application.mk 的方式
- 另一种是 CMake + CMakeLists.txt 的方式
这两种方式与 Android 代码和 c/c++ 代码无关,只是不同的构建脚本和构建命令。
如果非必须,不推荐使用 ndk-build 来构建,因为这样构建源码后,是无法使用方法跳转、方法提示等功能的!如果要改代码,就等于文本编辑器写代码。相反 CMake 是支持这些的,因此更有助于提高开发效率。所以这里就不详细说明 ndk-build 的使用步骤了,如果是新建项目就使用 CMake,如果是使用 ndk-build 的老项目,可以按照以下步骤转为 CMake。
3.1 操作步骤
软件环境:
Android Studio:3.6.3
JDK:1.8
NDK:21.2.6472646
3.1.1 修改 module 下的 build.gradle
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -stdlib=libc++ -fPIC -w"
arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_shared", "-DANDROID_ARM_MODE=arm", "-DANDROID_ARM_NEON=TRUE", "-DANDROID_PLATFORM=android-21"
}
ndk {
abiFilters 'armeabi-v7a'
}
}
}
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
version "3.10.2"
}
}
}
Application.mk中的 APP_PLATFORM 对应 arguments 中的 -DANDROID_PLATFORM;
Application.mk中的 APP_STL 对应 arguments 中的 -DANDROID_STL;
Application.mk中的 APP_ABI 对应 这里的 abiFilters;
Android.mk 中的 LOCAL_ARM_MODE 对应 arguments 中的 -DANDROID_ARM_MODE;
Android.mk 中的 LOCAL_ARM_NEON 对应 arguments 中的 -DANDROID_ARM_NEON。
3.1.2 新建 CMakeLists.txt
相关配置对应关系如下:
Android.mk | CMakeLists.txt |
---|---|
LOCAL_MODULE、LOCAL_SRC_FILES | add_library |
LOCAL_CFLAGS | add_definitions |
LOCAL_C_INCLUDES | include_directories |
LOCAL_STATIC_LIBRARIES、LOCAL_SHARED_LIBRARIES | add_library + set_target_properties |
LOCAL_LDLIBS | find_library |
配置完成后,Android.mk 和 Application.mk 就不需要了。
3.2 常见问题及解决方案
(1)CMake Error: CMake was unable to find a build program corresponding to “Ninja”. CMAKE_MAKE_PROGRAM is not set. You probably need to select a different build tool.
编译时报错,修改工程的 build.gradle 的 com.android.tools.build:gradle 为更高版本。
(2)java.lang.UnsatisfiedLinkError: dlopen failed: library “xxxx.so” not found
运行时报错,这是由于 so 库并没有打包进 apk,所以找不到。
在 Android Studio 中,会默认匹配 src/main/jniLibs 目录,如果没有目录需要自己手动创建。如果想要使用其他路径的库,需要手动指定。
在 module 的 build.gradle 中添加:
sourceSets {
main {
jniLibs.srcDirs = ['libs'] //这里的ibs替换为存放so库的文件夹,不能在jni文件夹下
}
}
建议全部放在 jniLibs,不需要额外的任何配置。
通常我们把第三方提供的 h 文件夹,放在 src/main/cpp/include 里面,so 库放在 src/main/jniLibs/armeabi-v7a(不同 CPU 架构不同目录)下。