跟我一起学NDK-CMake必知必会

CMake 简介

CMake 构建脚本是一个纯文本文件,您必须将其命名为 CMakeLists.txt,并在其中包含 CMake 构建您的 C/C++ 库时需要使用的命令。如果您的原生源代码文件还没有 CMake 构建脚本,您需要自行创建一个,并在其中包含适当的 CMake 命令

本部分将介绍您应该在构建脚本中包含哪些基本命令,以便指示 CMake 在创建原生库时使用哪些源代码文件。如需了解详情,请参阅 CMake 官方文档: https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html

CMake 常用语法

推荐使用 CMake 3.6.0 的版本,因为默认的 3.10.2 在日志显示上会有问题。在 build.gradle 文件 android 域中修改 CMake 的版本

1
2
3
4
5
6
7
8
android {
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.6.0'
}
}
}

CMake 的语法具体包含以下几类

  • CMake 的基础语法
  • c++ 文件编译成 so 的语法
  • so 动态库之前相互关联的语法

使用 # 作为注释开头

CMake 指令大小写不敏感,参数和变量大小写敏感

Android 中推荐使用小写

变量使用 ${} 方式取值,但是在 if 控制语句中直接使用变量名

逻辑操作,跟 python 有些类似

1
2
3
4
if(true)
message("this is true")
else()
message("this is false")

可以用于根据不同平台去编译不同平台的 so 库

set 指令

语法:set(VAR [VALUE])

显示定义变量,例如:

1
2
// 定义一个叫 name 的变量
set(name 刘小帅)

message 指令

向终端打印日志:编译过程打印日志;例如:

1
2
// 打印定义的 name 变量
message(${name})

cmake_minimum_required 指令

规定 CMake 最低版本,例如:

1
cmake_minimum_required(VERSION 3.4.1)

project 指令

定义 CMake 工程名称,例如:

1
project("testndk")

include_directories 指令

语法:include_directories([AFTER | BEFORE] [SYSTEM] dir1 dir2…)

可以用来向工程添加多个特定的头文件搜索路径(可以用<>引用),路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,例如:

1
2
//将 people 文件夹下的头文件添加到搜索路径
include_directories(people/)

add_library 指令

语法:add_library(name [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] [source])

将一组 cpp 源文件编译出一个库文件,并保存为 libname.so (lib 前缀是生成文件时 CMake 自动添加上去的)。其中有三种库文件类型,不写的话,默认为 STATIC

  • SHARED: 表示动态库,可以在(Java)代码中使用 System.loadLibrary(name) 动态调用
  • STATIC: 表示静态库,集成到代码中会在编译时调用
  • MODULE: 只有在使用 dyId 的系统有效,如果不支持 dyId,则被当作 SHARED 对待

add_library 命令也可以用来导入第三方的库:
add_library(libname [SHARED | STATIC | MODULE | UNKNOWN] IMPORTED)
例如,导入 opencv.so

1
add_library(opencv SHARED IMPORTED)

set_target_properties 指令

语法:set_target_properties(target1 target2 … PROPERTIES prop1 value1 prop2 value2 …)

这条指令可以用来设置输出的名称(设置构建同名的动态库和静态库,或者指定要导入的库文件的路径),例如:

1
2
3
4
// 设置 opencv 库的路径
set_target_properties(opencv
PROPERTIES IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libopencv_java3.so)

find_library 指令

查找 NDK 已经存在的 so 动态库,例如:

1
find_library(log-lib log)

语法:target_link_libraries(target library <debug | optimized> library2…)

共享库关联,以便相互调用函数,例如:

1
2
// 关联 native-lib 和 log-lib 两个动态库
target_link_libraries(native-lib ${log-lib})

经常用的常量

  • CMAKE_CURRENT_LIST_FILE 当前 CMake 文件的路径
  • CMAKE_CURRENT_LIST_DIR 当前 CMake 文件夹的路径
  • ANDROID_ABI 当前在打的 so 对应的 cpu 架构

这些路径对于引用其他的库非常有用

动态库的来源

动态库的来源总共就三类:

  1. 基于 cpp 源文件打的动态库
1
add_library(native-lib SHARED native-lib.cpp )
  1. NDK 中查找存在的动态库
1
find_library(log-lib log)
  1. 自己在 jniLibs 中添加的三方动态库
1
2
3
4
5
add_library(opencv SHARED IMPORTED)
// add_library 必须在前
set_target_properties(opencv
PROPERTIES IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libopencv_java3.so)

理解示例工程

现在我们再来看下之前示例工程创建的 CMakeLists.txt 的内容就会更加清晰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.10.2)
project("testndk")

add_library(
native-lib
SHARED
native-lib.cpp )

find_library(
log-lib
log )

target_link_libraries(
native-lib
${log-lib} )
  1. 设置了最低的 CMake 版本为 3.10.2
  2. 将当前 CMake 工程命名为 testndk
  3. 将 native-lib.cpp 文件打成动态库 libnative-lib.so
  4. 查找本地 log 库,并重命名为 log-lib
  5. 将 native-lib 和 log-lib 两个动态库关联,以便在 native-lib.cpp 内部使用 log 库的函数

CMake 构建产物

CMake 构建的产物在 build/intermediates 文件夹下,默认构建所有 cpu 架构的产物

构建产物

可以在 android 域的 defaultConfig 下指定需要构建的 cpu 架构

1
2
3
4
5
6
7
8
9
10
// 指定只打 v7a 和 v8a 的 so
android {
defaultConfig {
externalNativeBuild {
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
}
}