NDK-CMake初探

/ 0评 / 2

前言

随着对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文件为主要配置文件,具体可以查看下文的介绍。

file

生成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\(//.externalNativeBuild/cmake///),不过我们一般将so放在/src/main/jniLibs/下面,这一点我们也可以在CMakeLists.txt文件中进行设置。

#设置生成的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")

官方文档:https://developer.android.com/ndk/guides/cmake.html

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注