NDK(五):CMake基础语法及导入静动态库

android studio 下载 | 2019-01-23 22:54

这篇主要介绍CMake语法学习以及怎样把上一篇文章中编译生成的交叉编译库导入到Android项目中  由于其他原因耽误导致这篇文章间隔这么久才写好,导入的时候才发现上一篇文章中交叉编译后的库有点问题又进行重新修改。这篇文章是NDK系列的最后一篇了,算是大概知道了NDK系列的入门知识。

NDK系列文章

之前是使用,现在Google基本放弃了,都采用CMake

在学习CMake之前,我们或多或少了解过其他Make工具。其他的Make工具遵循不同的标准到时执行的Makefile格式不同,比如要保证在不同平台下编译,就需要一个跨平台的Make工具,CMake就是一个跨平台的构建工具。  Cmake并不直接构建出最终的软件,通过编写CmakeList.txt文件,根据目标用户的平台进一步生成对应的Makefile文件,从而达到跨平台的目的,如Android Studio就是通过ninja。所以可以这么理解为,编写Cmake语法,通过ninja,生成Makefile。

brew install cmake# 查看版本cmake -versioncmake version 3.13.2CMake语法入门官网语法手册(最好的资料)

学下CMake语法,可以参考官网的文档,进入官网,然后Resource --> Documentation --> 选择最新的Documentation --> Reference Manuals中的cmake-commands(7)。就可以看到所有的命令了。

main.c文件

#include <stdio.h>int main(){    printf("hello world\n");    return 0;}CMakeLists.txt文件

# CMake 最低版本号要求cmake_minimum_required (VERSION 3.4.1)# 项目信息(如果不设置该参数也可以正常运行)project (MyProject1)# 指定生成目标add_executable(Demo1 main.c)利用CMake命令编译生成执行文件

# 编译生成Makefile文件cmake .# 执行Makefile文件生成可执行文件make# 执行可执行文件,输出hello world./Demo1hello worldDemo2 多文件如果源文件有很多个,比如有main.c、hello.c、hello.h等,那么一个个写进去比较麻烦,如

main.c文件

#include "hello.h"int main(){    hello("world");    return 0;}hello.h文件

#ifndef HELLO_H#define HELLO_Hvoid hello(const char *name);#endif //HELLO_Hhello.c文件

#include <stdio.h>void hello(const char *name){    printf("Hello %s!\n", name);}CMakeLists.txt

# CMake 最低版本号要求cmake_minimum_required (VERSION 3.4.1)# 项目信息project (MyProject2)# 这样多的话,写进去就比较麻烦,导入只需要写源文件,.h可以不写add_executable(Demo2 main.c hello.c)# 查找当前目录所有源文件 并将名称保存到 DIR_SRCS 变量# 注意这样不能查找子目录,也不会自动往子目录找aux_source_directory(. DIR_SRCS)# 或者也可以使用如果通配符的方式,引入所有.c文件,同样保存到 DIR_SRCS变量中file(GLOB DIR_SRCS *.c)# $() 为引用变量DIR_SRCS的值add_executable(Demo2 ${DIR_SRCS})Demo3 多文件多目录新建文件夹helloDir,把Demo2中的hello.c、hello.h和新建一个新的CMakeLists.txt文件放到该目录下

main.c文件

// 记得修改头文件的引用路径#include "helloDir/hello.h"int main(){    hello("world");    return 0;}CMakeLists.txt

# CMake 最低版本号要求cmake_minimum_required (VERSION 3.4.1)# 项目信息project (MyProject3)# 添加当前目录下的所有源文件aux_source_directory(. DIR_SRCS)# 添加 helloDir 子目录下的cmakelistadd_subdirectory(helloDir)# 指定生成目标add_executable(Demo3 ${DIR_SRCS})# 添加链接库,hello为helloDir子目录的生成的链接库名称target_link_libraries(Demo3 hello)helloDir/CMakeLists.txt

# CMake 最低版本号要求cmake_minimum_required (VERSION 3.4.1)# 指定生成目标aux_source_directory(. DIR_LIB_SRCS)# 生成链接库,默认编译为静态库# add_library (hello ${DIR_LIB_SRCS})# 指定编译为静态库,与上面的一样add_library (hello STATIC ${DIR_LIB_SRCS})# 指定编译为动态库add_library (hello SHARED ${DIR_LIB_SRCS})add_library (hello STATIC ${DIR_LIB_SRCS})NDK项目之CMake通过Android Stuido创建支持NDK的项目,就可以看到,多了一个CMakeList.txt文件。在Android Studio 2.2及其以上,构建原生库的默认工具就是CMake。

# 设置cmake最低支持版本cmake_minimum_required(VERSION 3.4.1)# 编译library库add_library(              # 设置编译后的library名称             native-lib             # 设置library模式,STATIC为静态库,如果想编译为动态库,则修改为SHARED             STATIC               # 设置编译使用到的源代码,如果添加了源代码,都需要修改此处添加源代码的路径             src/main/cpp/native-lib.cpp )# 查找library,因为NDK中已经有一部分预构建库,已经被配置为cmake搜索路径的一部分,所以,可以直接添加库的名称。find_library(             # 此处为查找名称为log的库,将绝对路径赋值到变量log-lib             log-lib             log )# 链接librarytarget_link_libraries(                       # 链接自己编写的native-lib库和上面查找到的lib库                       # 位置不能更改,native-lib为目标的库,必须放在前面                       native-lib                       ${log-lib} )# 如果上面不预先使用find_library的话,也可以直接使用target_link_libraries( native-lib                        log)同时build.gradle文件也多了一些配置

android {    compileSdkVersion 28    defaultConfig {        externalNativeBuild {            // 主要是配置了cmake的命令参数            cmake {                cppFlags ""                // 设置编译c/c++ 源文件的cpu类型                // 如果不设置,连接不同的设备,gralde会自动根据连接的设备,如armeabi-v7a架构的手机,则编译出armeabi-v7a,如模拟器,则编译出x86。                // 由于考虑编译出来后包的大小,一般实际开发中,都只是编译兼容性最多的armeabi-v7a架构                abiFilters 'armeabi-v7a'            }        }    }    externalNativeBuild {        // 主要定义了CMake的构建脚本CMakeLists.txt的路径        cmake {            // 当前的CMakeLists.txt与build.gradle在同一个目录下            // CMakeLists.txt也可以在其他路径下,比如可以在cpp目录下创建一个CMakeList.txt配置cpp目录下的源文件            path "CMakeLists.txt"        }    }}添加预编译库在上一篇文章中,分别对怎样编译Android平台上的静、动态库进行介绍,接下来,就把其编译好的库(即预编译库)放到Android Stuido项目中

引入libmain.a的静态库,在新建armeabi-v7a在cpp文件下,把libmain.a放入其中。

cmake_minimum_required(VERSION 3.6)add_library( native-lib             SHARED             src/main/cpp/native-lib.cpp )# hello-jni: 变量名 最终会生成的so名字# SHARED: 动态库  STATIC:静态库add_library(hello-jni SHARED hello-jni.c)# 引入预编译库# Main:引入的库的名字,可以随便定义,但是要和下面的保存一致# STATIC:声明导入的是动态库或者静态库,STATIC为静态库# IMPORTED:表示这个库是以导入的方式导入add_library(Main STATIC IMPORTED)# 设置导入的路径# Main:库的名称# 设置目标属性 导入路径 当前库的路径# ${ANDROID_ABI}引用的变量,在上面的build.grale中,我们设置abiFilters为armeabi-v7aset_target_properties(Main PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libmain.a)find_library( log-lib              log )target_link_libraries(hello-jni Main)修改native-lib.cpp文件,修改后如下

#include <jni.h>#include <string>#include <android/log.h>// 使用上面的log库// 该文件为c++,库为c,因此需要添加extern "C"extern "C" int main();extern "C" JNIEXPORT jstringJNICALLJava_cn_guidongyuan_studyndk_MainActivity_stringFromJNI(        JNIEnv *env,        jobject /* this */) {    // 如果输出结果与库中main定义的返回值一致,则表示调用成功    __android_log_print(ANDROID_LOG_ERROR,"Log","%d", main());    std::string hello = "Hello from C++";    return env->NewStringUTF(hello.c_str());}引入动态库引入libTest.so动态库,动态库必须放在src/jniLibs/armeabi-v7a(不同CPU架构不同目录)下,否则不会打包进去

cmake_minimum_required(VERSION 3.6)add_library( native-lib             SHARED             src/main/cpp/native-lib.cpp )find_library( log-lib              log )# 设置库的查找路径# set方法 定义一个变量,此处为在原来的CMAKE_CXX_FLAGS变量上,添加-L加库的查找路径# 因为项目中的native-lib.cpp为C++,因此存在C++则使用CMAKE_CXX_FLAGS,完全没有就使用CMAKE_C_FLAGSset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")# 链接动态库的名字,库名词为libmain.so,这里必须使用main,就会结合上面设置的库查找路径以及库名称查找到对应的库target_link_libraries( native-lib                       ${log-lib}                       main)message("ANDROID_ABI : ${ANDROID_ABI}")# set 方法 定义一个变量#CMAKE_C_FLAGS = "${CMAKE_C_FLAGS} XXXX"# -L: 库的查找路径 libTest.so# 如果库用c写的,则用CMAKE_C_FLAGS,如果使用c++或者c++和c混合使用,则用CMAKE_CXX_FLAGSset(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")find_library(log-lib log)# 此处的Test名字,必须为动态库的名称,查找路径在上面的set设置了target_link_libraries(hello-jni Test ${log-lib} )修改native-lib.cpp文件,修改内容与上面静态库一样

CMake 入门实战

很详细的一篇文章,从实例入手,讲解 CMake 的常见用法,单独作为一种语法来学习,而不是在Android Studio项目中进行介绍。

linux CMakeLists.txt 语法

可以看一下其中的CMake常用命令章节,基本列出了CMake语法中常见的语法

CMake官网

Google官网Cmake教程

CMake手册

CMake手册(中文版)

Android NDK开发(一) 使用CMake构建工具进行NDK开发

简单介绍了CMake在Android Stuido中的配置,各个变量的作用