NDK 开发之 ndk-build 的使用

巢承安
2023-12-01

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.mkApplication.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.mkCMakeLists.txt
LOCAL_MODULE、LOCAL_SRC_FILESadd_library
LOCAL_CFLAGSadd_definitions
LOCAL_C_INCLUDESinclude_directories
LOCAL_STATIC_LIBRARIES、LOCAL_SHARED_LIBRARIESadd_library + set_target_properties
LOCAL_LDLIBSfind_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 架构不同目录)下。

 类似资料: