前言
随着对Android学习的越来越深入,开始接触到NDK相关知识了,想当年还为了以后没机会使用C/C++而心伤,现在终于有机会在Android上使用C/C++了,下面就来介绍一下Android Studio2.2以后带来的新特性CMake。
官方文档
关于在Android Studio2.2及以后版本上面使用CMake构建NDK项目,可以查看官方文档:传送门。里面详细介绍了CMake的安装以及使用,本篇博客也是基于此文档进行进一步说明。关于NDK环境的下载以及Demo工程的创建可以查看贴出的官方地址》》》传送门!!!
运行Demo
按照官方文档中的新建一个NDK工程,目录结构如下:
1.对应C/C++源文件(.c、.cpp、*h)
2.CMakeLists.txt文件为主要配置文件,具体可以查看下文的介绍。
生成SO文件。点击状态栏上面的Bulid->Rebuild Project,生成的so在\app\build\intermediates\cmake\debug\obj\路径下。
如下图所示,默认生成支持全部平台的so。不过一般来说,我们仅仅需要arm64-v8a、armeabi、armeabi-v7a甚至只需要armeabi即可。
CMakeLists.txt文件的介绍
CMakeLists.txt类似于我们以前使用ndk-build时使用的Android.mk,主要是用来配置C/C++文件的依赖关系以及生成so等,下面就以官方的Demo来做介绍。具体请查看下面贴出的中文注释。
# 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.
#创建一个名字为native-lib的动态链接库(实际生成为libnative-lib.so)
#你可以指定为STATIC(*.a)或者SHARED(*.so),可以通过这个生成多个动态链接库
#通过第三个参数指定源码路径,多个源码使用空格隔开,路径是相对于CMakeLists.txt文件来的
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp )
# 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.
# 指定另外一个动态链接库连接到指定的动态链接库,这里是将log-lib这个
# 链接到native-lib,可以指定多个,使用空格隔开
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
# 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.
# 使用这个命令可以导入NDK本地的链接库
# 下面这个是导入本地的log链接库并命名为log-lib,通过${log-lib}引用
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
add_library命令
命令说明:add_library可以引入源文件(.c、.cpp)并将其编译为so文件,也可以导入已经编译好的so到项目中。基本用法
add_library( #指定生成so的名字(libnative-lib)
native-lib
#指定生成为SHARED版本(*.so)
SHARED
#制定源文件位置,多个使用空格隔开
native-lib.cpp )
add_library一共有三个参数,第一个为库名称,最终的so文件名为lib库名称.so
,不过最后使用的时候只需要使用库名称即可。比如在构建脚本中指定“native-lib”作为共享库的名称,CMake 将创建一个名称为 libnative-lib.so
的文件。不过,在 Java 代码中加载此库时,只需要加入如下代码。
static {
System.loadLibrary(“native-lib”);
}
第二个参数为库的版本,有SHARED以及STATIC可选,SHARED生成动态链接库(.so),STATIC生成静态链接库(.a)
第三个参数为编译库所需要的源文件位置,如果有多个源文件,那么使用空格或者回车隔开即可。
add_library的第三个参数,源码路径是相对于CMakeLists.txt文件位置,当然使用${PROJECT_SOURCE_DIR}/src/main/cpp/
写死绝对路径也可以,就是比较麻烦。
当然,当我们的C文件比较多的时候,一个个的添加无疑是非常麻烦的,我们可以直接如下配置
# 批量导入源文件,如果有子目录,需要额外指定,路径相对CMakeLists.txt文件位置,*号不能省略
file(GLOB allFile *.h *.c *.cpp ./test/*)
add_library(
native-lib
SHARED
# 定义所有源文件
${allFile})
如果工程中有多个源文件需要编为不同的so,那么在CMakeLists.txt中使用多个add_library,并且分别指定好源文件以及生成的名字即可。Demo如下。将使用native-lib.cpp编译出libnative-lib.so,使用native-lib2333.cpp编译出libnative-lib233.so
add_library(native-lib
SHARED
native-lib.cpp)
add_library(native-lib233
SHARED
native-lib2333.cpp)
添加其他so库
方式一:使用add_library与set_target_properties
根据官方文档如果我们需要导入已经存在的so文件到项目中,那么在使用add_library的同时也需要使用set_target_properties指定so文件的位置,如果so文件有头文件,那么还需要使用include_directories指定头文件的位置,示例如下,具体介绍请查看中文注释。
# 假设需要导入libfmod.so
add_library(
fmod
# 指定为SHARED模式,如果是.a,则是STATIC
SHARED
# 指定为外部导入的共享库
IMPORTED )
set_target_properties( #这里是上面指定的名字
fmod
# 这一行默认这样写
PROPERTIES IMPORTED_LOCATION
# 指定so文件存在的位置,全路径
# CMAKE_SOURCE_DIR : CMakeLists.txt所在的路径
# CMAKE_ANDROID_ARCH_ABI : 当前的CPU架构
${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libfmod.so)
# 如果so文件需要头文件,通过这个命令引入头文件,也可以使用相对于CMakeLists.txt文件位置
# 头文件位置/app/src/main/cpp/imported-lib/include/
# ${PROJECT_SOURCE_DIR} : CMakeLists.txt所在的路径
include_directories(${PROJECT_SOURCE_DIR}/imported-lib/include)
target_link_libraries(
native-lib
fmod # 上面定义的名字 链接到 libnative-lib.so里面去
)
include_directories:源码路径是相对于CMakeLists.txt文件位置
方式二
# 设置so文件的路径
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
target_link_libraries(
native-lib
log # 自动寻找liblog.so 链接到 libnative-lib.so里面去
fmod # 自动寻找libfmod.so 链接到 libnative-lib.so里面去
fmodL # 自动寻找libfmodL.so 链接到 libnative-lib.so里面去
find_library与target_link_libraries
命令说明:预构建的 NDK 库已经存在于 Android 平台上,因此,我们无需再构建或将其打包到 APK 中。由于 NDK 库已经是 CMake 搜索路径的一部分,所以我们不需要在本地 NDK 安装中指定库的位置 - 只需要在 CMake 中设置希望使用的库的名称,并通过target_link_libraries命令将其关联到我们自己的原生库。基本用法
find_library( #指定本地链接库的引用名,通过${log-lib}引用
log-lib
#指定NDK本地链接库的名字
log )
# 将上面指定的log库链接到libnative-lib.so中,也可以链接多个
# 通过空格隔开即可
target_link_libraries( native-lib ${log-lib} )
Cmake添加编译参数
在我们使用Cmake编译so的时候,默认使用的Android平台版本为minSdkVersion所指定的版本,如果我们想指定其他的版本,那么就需要添加一些配置参数。语法如下,参数名使用-D开头,多个参数使用,号隔开。arguments -DVAR_NAME=ARGUMENT,-DVAR_NAME=ARGUMENT
android {
...
defaultConfig {
...
// This block is different from the one you use to link Gradle
// to your CMake build script.
externalNativeBuild {
cmake {
...
// Use the following syntax when passing arguments to variables:
// arguments -DVAR_NAME=ARGUMENT.
arguments -DANDROID_ARM_NEON=TRUE,
// If you're passing multiple arguments to a variable, pass them together:
// arguments -DVAR_NAME=ARG_1 ARG_2
// The following line passes 'rtti' and 'exceptions' to 'ANDROID_CPP_FEATURES'.
-DANDROID_CPP_FEATURES=rtti exceptions
}
}
}
buildTypes {...}
// Use this block to link Gradle to your CMake build script.
externalNativeBuild {
cmake {...}
}
}
举例:设置CMake编译版本为android-21
android {
defaultConfig {
externalNativeBuild {
cmake {
arguments -DANDROID_PLATFORM=android-21
}
}
}
}
更多可选参数请查看文末官方文档(需科学上网),或者你也可以看看这篇博客
其他
设置so文件生成位置
在上面我们说过了,so默认生成位置为\app\build\intermediates\cmake\debug\obj\(
#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
设置SO文件支持的cpu类型
CMake默认生成全部cpu架构支持的so文件,但是就像上面所说的,我们一般不需要这么多的,在app/下的build.gradle文件中,配置一下即可,下面的例子是只生成arm64-v8a架构的CPU。
#其他的省略
android {
.....
defaultConfig {
.....
externalNativeBuild {
cmake {
cppFlags ""
//我们的so只生成一个平台
abiFilters 'arm64-v8a'
}
}
ndk {
//这一个是打包apk的时候,只打包一个平台的,如果不设置这个,apk里面还是有多种平台so
abiFilters "arm64-v8a"
}
}
.....
}
缩减so库
cmake打包出来的release是包含符号表的,可以strip一下,去掉符号表
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")