「NDK」二 CMake详解
一 CMake是谁
在版本2.2及以上,构建原生库的默认工具变成了CMake.CMake是一个跨平台的构建工具,能够输出各种各样的makefile或者project文件。
CMake并不直接构建出最终的软件,而是产生其他工具的脚本如Makefile,然后再依照这个工具的构建方式使用。
CMake是一个比make更高级的编译配置工具,它可以根据不同的平台,不同的编译器,生成相应的Makefile或者vcproj项目,从而达到跨平台的目的。
Android Studio利用CMake生成的ninja,ninja是一个小型的关注速度的构建系统。这玩意儿是什么,我们不用去太多关注,只需要知道怎么配置cmake就可以了。
在我们创建项目的时候,如果勾选Include C++ Support ,就会在main的同级目录下生成一个CMakeLists.txt,下面来一一介绍
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
#1.设置创建本地库所需CMake最小版本
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#库的创建和命名,将其设置为STATIC或者SHARED
#并提供其源代码的相对路径
#您可以定义多个库,CMake为您构建他们
#用APK自动打包共享库
#2.编译library
add_library( # Sets the name of the library.设置库的名称
native-lib
# Sets the library as a shared library.设置为共享库
#设置library模式
#SHARED 模式会编译so文件,STATIC模式不会编译
SHARED
# Provides a relative path to your source file(s).
#设置原生代码路径
src/main/cpp/native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
#搜索指定的预构建库并将路径存储为变量,因为CMake默认包含搜索路径中的系统库,您只需指定要添加的公共NDK库的名称即可。
#CMake在完成构建之前验证该库是否存在
find_library( # Sets the name of the path variable.设置路径变量的名称
log-lib
# Specifies the name of the NDK library that 指定希望CMake定位的NDK库的名称
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
#指定库CMake 应该链接到目标库,您可以链接多个库,作为每个构建脚本中定义的库,预构建的第三方库和系统库
target_link_libraries( # Specifies the target library.指定目标库
native-lib
# Links the target library to the log library
# included in the NDK.
#将目标库链接到日志库,包含在NDK中
${log-lib})
对应编号代码解释:
【1】指定CMake所需最小版本,自动生成,无需更改
【2】add_library():用来设置编译生成原生库的相关属性:
native-lib : 你要生成原生库的名称
SHARE : 设置库的类型,分为SHARE 动态链接库 STATIC 静态链接库
src/main/cpp/native-lib.cpp:
您写的c/c++代码的相对路径,目前我cpp目录中有个native-lib.cpp文件,如果再添加一个native-libw2.cpp,则可直接在下面添加:
add_library( # Sets the name of the library.设置库的名称
native-lib
# Sets the library as a shared library.设置为共享库
#设置library模式
#SHARED 模式会编译so文件,STATIC模式不会编译
SHARED
# Provides a relative path to your source file(s).
#设置原生代码路径
src/main/cpp/native-lib.cpp
src/mainn/cpp/native-lib2.cpp)
【3】 添加其他预构建库
添加预构建库与为 CMake 指定要构建的另一个原生库类似。不过,由于库已经预先构建,您需要使用 [IMPORTED
]标志告知 CMake 您只希望将库导入到项目中也要用到add_library(),然后,你需要使用set_target_properties命令指定库的路径,比如我们将编译的ffmpeg相关so库集成到Android项目时所作的设置一样,已经编译好的so库就是这里所说的预构建库,在引入时只需要这般配置(以ffmpeg中的avcodec库为例)
# avcodec
add_library( avcodec-57
SHARED
#IMPROTED说明只是导入不需构建
IMPORTED )
set_target_properties(
#指定目标库
avcodec-57
#指定要定义的参数
PROPERTIES IMPORTED_LOCATION
#指定库路径 armeabi-v7a文件夹下的so库
${distribution_DIR}/armeabi-v7a/libavcodec-57.so )
另外确保CMake可以在编译时定位到你的头文件,你需要使用include_directories()命令,指定包含头文件的路径
include_directories(你的真实路径/include/ )
如果要将预构建库关联到你的原生库,需要将其添加到CMake构建脚本的target_link_libraries()命令中:
target_link_libraries(native-lib avcodec-57 ${log-lib})
在你构建应用时,Gradle会自动将导入的库打包到APK中。
【4】添加NDK API
find_library():Android NDK 提供了一套实用的原生 API 和库,因其已经存在于Android平台中,而且NDK库已是CMake搜索路径的一部分,所以只需要提供要使用的库名称
target_link_libraries():上面提到过,这里就是将你指定的NDK库关联到你的原生库native-lib里,之后你的原生库就可以在 log 库中调用函数了。
find_library( # Sets the name of the path variable.设置路径变量的名称
log-lib
# Specifies the name of the NDK library that 指定希望CMake定位的NDK库的名称
# you want CMake to locate.
log)
target_link_libraries( # Specifies the target library.指定目标库
native-lib
# Links the target library to the log library
# included in the NDK.
#将目标库链接到日志库,包含在NDK中
${log-lib})
- log-lib : 依赖库的别名
- log : 你要依赖的NDK库名称
- native-lib : 您自己的库,和add_library中的名称一致
- ${log-lib} : 指定需要关联的库的名称
二.使用CMake生成so库
现在编译项目,我们会在 <项目目录>\app\build\intermediates\cmake\debug\obj\armeabi 下面就可以看到生成的动态链接库。
如果觉得上面的so库路径太深了,可以自行配置,只需要在顶层的CMakeLists.txt中添加下面代码即可:
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
编译运行后,会在app/src/main 中看到jniLibs目录(这里是配置到了系统默认的路径)如果配置到其他路径比如libs下,需要在gradle中使用:
//引入so库
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
来进行制定。
然后在app下的build.gradle中指定abi abiFilters 如果不指定,会生成所有当前支持的abi库
这里延展下,什么是ABI?
ABI(Application binary interface)应用程序二进制接口。不同的cpu与指令集的每种组合都有定义的ABI(应用程序二进制接口),一段程序只有遵循这个接口规范才能在该cpu上运行。所以同样的代码为了兼容多个不同的cpu,需要为不同的ABI构建不同的库文件:
armeabi设备只兼容armeabi;
armeabi-v7a设备兼容armeabi-v7a、armeabi;
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
X86设备兼容X86、armeabi;
X86_64设备兼容X86_64、X86、armeabi;
mips64设备兼容mips64、mips;
mips只兼容mips;
当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库.
defaultConfig {
......
......
......
externalNativeBuild {
cmake {
cppFlags ""
abiFilters 'x86','armeabi-v7a';
}
}
}
指定之后只会生成相应的版本abi ,也就是只会生成x86,armeabi-v7a下的so文件。
那么abiFilter是作什么用的呢?貌似我们在我们的android项目中经常会看到类似的配置:
defaultConfig {
...
ndk {
abiFilters "armeabi-v7a", "x86";
}
}
两者的配置功能类似:
第一个的配置决定了生成的支持什么abi的so库。
第二个的作用呢?举个例子:
如果我们的项目中引入了某个sdk,这个sdk支持armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 五种ABI,但是我们的目中只支持armeabi-v7a、x86两个目录中,应用安装在armeabi-v7a、x86架构的设备上是没有问题的,但是安装在其他架构的设备上就会出现如下异常:
java.lang.UnsatisfiedLinkError: Couldn't load native-lib from loader dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.yl.ndkdemo-1.apk"]
nativeLibraryDirectories=[/data/app-lib/com.yl.ndkdemo-1, /vendor/lib, /system/lib, /system/lib/arm]]]: findLibrary returned null
这种该怎么解决呢?
- 方法一 将多出来的ABI删除掉,可行,但如果是远程库引用就无效了。
- 方法二 限制安装包ABI的架构类型,不管sdk或者项目中有多少种ABI架构,最终打包进APK的so库都以abiFilters中规定的为准。
也就是说,如果我们的项目中有五种ABI库,但是abiFilters设置了两种ABI支持,那么最终打包的APK中只会包含这两种ABI库。
在实际应用中,除了上述问题,还会经常遇到这种报错:
ABIs [armeabi] are not supported for platform. Supported ABIs are [armeabi-v7a, arm64-v8a, x86, x86_64].
原因在于 NDK-r17版本,已经去掉了armeabi、mips、mips64的ABI支持。
碰到上述报错,只需去掉abiFilters中的armeabi选项即可,放心!armeabi-v7a会兼容armeabi。