使用CMake构建android动态连接口

CMake是个一个开源的跨平台自动化建构系统。CMake并不直接建构出最终的软件,而是产生标准的建构工程文件(如Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建构方式使用。在这里,CMake生成的是Ninja的构建工程(Ninja是一个专注于速度的小型构建系统[1],由Evan Martin于2010年在Chrome团队工作时开发),然后再用Ninja来生成动态链接库

基于构建效率和代码结构考虑,我们后续的jni项目建议使用cmake进行c++项目的构建

一个简单的例子

CMake的配置文件取名为CMakeLists.txt。

########## CMakeLists.txt ##########

# 最低版本要求,版本要求大于等于3.4.1
cmake_minimum_required(VERSION 3.4.1)

# 定义动态链接库的名字、类型和源码
add_library( # Specifies the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             main.c)

# 定义需要链接的其他库
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                        native-lib # 这里声明目标库
                        android    # 随后目标库需要链接的库
                        log)       # 可以继续往下加,这里都是NDK里的

在当前目录里创建源码

// main.c

#include <android/log.h>
#include <jni.h>

#define TAG "MyApplication"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)

#define JNI_API_DEF(f) Java_com_nd_app_factory_imapp0nd_MainActivity_##f

JNIEXPORT jint JNICALL
JNI_API_DEF(show)(JNIEnv *env, jobject thiz) {
  LOGW("hello world");
  return 5;
}

在这里需要的文件都生成好了,我们需要调用构建命令

${ANDROID_SDK_HOME}/cmake/3.6.3155560/bin/cmake \
-H. \
-B./arm64-v8a \
-G"Android Gradle - Ninja" \
-DANDROID_ABI=arm64-v8a \
-DANDROID_NDK=${ANDROID_SDK_HOME}/ndk/21.3.6528147 \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=./release/obj/arm64-v8a \
-DCMAKE_MAKE_PROGRAM=${ANDROID_SDK_HOME}/cmake/3.6.3155560/bin/ninja \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_SDK_HOME}/ndk/21.3.6528147/build/cmake/android.toolchain.cmake \
-DANDROID_NATIVE_API_LEVEL=23 \
-DANDROID_TOOLCHAIN=clang

其中从第四个参数开始,都是自定义的,会写入到最终的构建系统的

运行完构建命令后,如果我们sdk和ndk的版本/路径都没有问题,会输出

 $ ${ANDROID_SDK_HOME}/cmake/3.6.3155560/bin/cmake \
-H. \
-B./arm64-v8a \
-G"Android Gradle - Ninja" \
-DANDROID_ABI=arm64-v8a \
-DANDROID_NDK=${ANDROID_SDK_HOME}/ndk/21.3.6528147 \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=./release/obj/arm64-v8a \
-DCMAKE_MAKE_PROGRAM=${ANDROID_SDK_HOME}/cmake/3.6.3155560/bin/ninja \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_SDK_HOME}/ndk/21.3.6528147/build/cmake/android.toolchain.cmake \
-DANDROID_NATIVE_API_LEVEL=23 \
-DANDROID_TOOLCHAIN=clang

-- Configuring done
-- Generating done
-- Build files have been written to: /Users/mk/security/test/cmakeTest/arm64-v8a

然后我们再运行Ninja生成最终的结果

$ $ANDROID_HOME/cmake/3.6.3155560/bin/ninja -C ./arm64-v8a/
ninja: Entering directory `./arm64-v8a/'
[1/1] Linking C shared library release/obj/arm64-v8a/libnative-lib.so

最终我们就得到了libnative-lib.so

使用自定义高版本cmake

由于Android Sdk里自带的cmake版本太低,有些API或者子模块可能会不存在,所以,我们有时候会使用自定义的cmake版本。 测试发现新版本的cmake(3.19.2)也是支持${ANDROID_SDK_HOME}/cmake/3.10.2.4988404/bin/ninja(1.8)的,所以我们可以仅替换掉cmake的路径,以及-G参数(新版本cmake没有 Android Gradle - Ninja这个generator里,统一使用Ninja

修改后的命令行变成

${where-you-install-cmake}/cmake \
-H. \
-B./arm64-v8a \
-G"Ninja" \
-DANDROID_ABI=arm64-v8a \
-DANDROID_NDK=${ANDROID_SDK_HOME}/ndk/21.3.6528147 \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=./release/obj/arm64-v8a \
-DCMAKE_MAKE_PROGRAM=${ANDROID_SDK_HOME}/cmake/3.10.2.4988404/bin/ninja \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_SDK_HOME}/ndk/21.3.6528147/build/cmake/android.toolchain.cmake \
-DANDROID_NATIVE_API_LEVEL=23 \
-DANDROID_TOOLCHAIN=clang