cmake 怎么用

近日,谷歌在纽约如期举行了主题为“谷歌制造”(Made By Google)的硬件发布会,推出了Pixel手机、二合一平板,以及音箱等一系列新品硬件。一个月以来,包含谷歌、微软、苹果、亚马逊在内的美国四大科技巨头都相继发布了旗下最新硬件。而相比其他三家,谷歌本次的硬件品类最为全面,也彰显出更多的硬件野心。

作者简介

大家周一好,新的一周,勇往直前!

本篇转自 xong文章,分享了关于关于在Android中使用CMake你所需要了解的一切的相关内容,一起来看看!希望大家喜欢。

xong的博客地址:

https://juejin.im/user/58c382750ce4630054690774

前言

相信大家在开发的过程中,都或多或少的接触过JNI,然后每次要接触JNI的时候,倒吸一口冷气,太难啦!

只有Java代码和C++代码 还好,在新建项目的时候把那个 "Include C++ support"勾选上,然后一路next,最后finish,一个简单的带有C++代码的Android项目就算完成了,然后在看下CMakeLists.txt怎么写的,照猫画虎就可以了,但是实际开发中,并不简单,因为底层(C++)那里给过来的有可能是.a静态库,也有可能是.so动态库,然后如何集成进项目里就是:一脸懵逼,二脸茫然,三脸不知所措。由于客观事实只能去百度,然后百度到的全是用Android.mk实现的居多,然而现在都Android Studio 3.1+时代了,还mk?关于CMake的资料又很少,所以就有了本文。

在去年在公司还是实习的时候,老大丢给我一个.a静态库(当时并不知道这是一个静态库),很是好奇,很想知道.a是怎么构建出来的,a和so的区别又是什么,总之一大堆疑问,在转正后,也尝试过要构建一个.a静态库,无奈!百度到的都是用mk的咯,找到一篇CMake的,但竟然要去Linux下面编译,还要什么自定义工具链,总之,麻烦的一批。

所以就有了这一系列,本系列不阐述什么是CMake,什么是NDK,什么是JNI,只是写一下在Android中使用CMake常遇的问题,解决方法是什么。

本系列涉及到的有:

  • 初次使用CMake构建native项目
  • 如何将现有的cpp代码集成到项目中
    • 拷贝源码
    • 编译成库文件
  • CMake链接a静态库以及so动态库及动态库和静态库的区别

开始

初次使用CMake构建native项目

这个就很简单啦,新建项目的时候把 include C++ support 勾选上就可以了,但,我们还是自己动手来一遍,不依靠IDE,看看可不可以,新建普通项目,名字为:AndCmake,新建完成后如下:

cmake 怎么用

新项目,什么都没有,我们都知道用CMake的话,必须要有CMakeLists.txt(注意名字不能写错),第二个就是需要的cpp资源啦,这就很简单了,在src/main目录下新建就好了,为了整洁在main目录下新建cpp文件夹,且在里面新建CMakeLists.txt和native_hello.cpp文件,然后在去CMakeLists.txt简单的配置下就好了,完成后,如下:

../src/main/cpp/native_hello.cpp

//

// Created by xong on 2018/9/28.

//

#include<jni.h>

#include <string>

extern "C"JNIEXPORT

jstring JNICALL

Java_com_xong_andcmake_jni_NativeFun_stringFromJNI(JNIEnv *env, jclass thiz)

{

std::string hello = "Hello,I from C++";

returnenv->NewStringUTF(hello.c_str());

}

这里需要说明以下,Java_com_xong_andcmake_jni_是去java的一层一层的包下面去寻找,比如这个就是去com.xong.andcmake.jni下面找,后面的NativeFun_stringFromJNI就是类和方法名了。函数的形参两个必须是这样的,第二个形参可以写成"jobject",我最近写JNI的时候,提示改成"jclass"但是,"jobject"也不能算错,也是对的。

./src/main/cpp/CMakeLists.txt

# CMake最低版本

cmake_minimum_required(VERSION 3.4.1)

# 将需要打包的资源添加进来

add_library(

# 库名字

native_hello

# 库类型

SHARED

# 包含的cpp

native_hello.cpp

)

# 链接到项目中

target_link_libraries(

native_hello

android

log

)

这就把C++部分写好了。修改../app/build.gradle,修改后如下:

android {

...

defaultConfig {

...

externalNativeBuild {

cmake {

arguments '-DANDROID_STL=c++//www.58yuanyou.com_static'

}

}

...

}

...

externalNativeBuild {

cmake {

path 'src/main/cpp/CMakeLists.txt'

}

}

...

}

写对应的Java层代码,在com.xong.andcmake包下新建jni,然后新建NativeFun类,代码如下:

packagecom.xong.andcmake.jni;

/**

* Create by xong on 2018/9/28

*/

publicclassNativeFun{

static{

System.loadLibrary( "native_hello");

}

publicstaticnativeString stringFromJNI();

}

调用NativeFun.stringFromJNI(),查看是否有正确的信息输出:这就很简单啦,在Activity中添加一个TextView然后显示下就好了,正确的话,应该会有如下显示:

cmake 怎么用

OK,这样我们的第一个带有CPP代码的APP就算完成了,然后我们来思考一下,先来看native_hello.cpp的代码:

//

// Created by xong on 2018/9/28.

//

#include<jni.h>

#include <string>

extern "C"JNIEXPORT

jstring JNICALL

// 这里可不可以优化一下?

Java_com_xong_andcmake_jni_NativeFun_stringFromJNI(JNIEnv *env, jclass thiz)

{

std::string hello = "Hello,I from C++";

// 这里呢?

returnenv->NewStringUTF(hello.c_str());

}

对于我们来讲:

  1. 尽量不写重复代码
  2. 代码要高效

对于上面的函数名来讲,Java_com_xong_andcmake_jni_,假如我们在Java中对应的native类和方法,感觉在这个包中不合适,要换下包名,但是我这个类中有很多native方法,只要一换包,那么对应的cpp中的函数名就要挨个改,函数少还好办,假如有1000个,改起来真的是不要太爽,而且容易丢,那么有没有办法来尽量的减少工作量呢?类和包是相对固定的,那么我们能不能把包名抽取出来用一个表达式来表示呢?没错!就是宏定义啦!

对于下面返回的那一句,我为啥要这样写呢,因为用Android Studio创建一个带有cpp项目的时候,里面就是这样写的,然后就很自然的抄来了,但是我们想一下,有这样写的必要吗?可以看到的是在最后返回的时候,string对象又经过了一层转换点进去后可以发现,是将 string对象又转成了char数组(当然在C++中是char*指针还是const的,这里不需要知道const是啥,只需要知道,c_str()其实是又将string对象转成了char[]数组),然后我们这样写是不是就…多此一举,让计算机多做了一些事情呢?所以改完后的代码如下:

//

// Created by xong on 2018/9/28.

//

#include<jni.h>

# define XONGFUNC(name)Java_com_xong_andcmake_jni_##name

extern "C" JNIEXPORT

jstring JNICALL

XONGFUNC(NativeFun_stringFromJNI)(JNIEnv *env, jclass thiz)

{

returnenv->NewStringUTF( "Hello,I from C++");

}

这样的话,假如NativeFun不在jni包下了,而是在jnia包下,那么我们只需要将上面的宏定义改下就好了;因为我们没有对string做操作,那么我们就不需要它啦,直接返回就好了,这样计算机就不那么累了。make project后,发生了什么?

接触过JNI的同学应该了解,项目里面添加了cpp代码,apk会变的很大,那么原因嘞,那么我就来捋一捋,cpp的代码再make project后都发生了什么,都很清楚会生成so,那么…在默认的情况下会生成几个?

打开 ../app/build/intermediates/cmake/debug/obj 目录如下:

cmake 怎么用

在默认的情况下会生成4个,v7a的是最大的,x86_64是最小的,默认情况下,这4个是都会打到apk里面的(因为不清楚到底是往那台手机上安装呀,只能将所有的资源都打到apk里面了),要想缩小apk,那么就把需要的so打入apk就好了。然后,问题来了,怎么才能生成规定的so库呢?上代码,如下:

apply plugin: 'com.android.application'

android {

...

defaultConfig {

...

externalNativeBuild {

cmake {

arguments '-DANDROID_STL=c++_static'

}

ndk {

abiFilters "armeabi-v7a", "x86"

}

}

...

}

...

}

...

在../app/build.gradle中添加ndk标签,且在里面写上需要生成的架构名称就可以了,重新build下项目,如下图:

cmake 怎么用

这样打入apk中的就只有v7a和x86两种架构的so库啦!下篇我们来讲解一下 现有的cpp代码集成到项目中有几种方式,以及如何操作。如何将现有的cpp代码集成到项目中?

这个在写JNI的时候就很常见了,比如json库,C++自己是没有提供json库的,然后我们在写JNI的时候,通常需要和上层交互数据,较简单的就是json了,那么就拿json来做讲解吧。首//www.58yuanyou.com先来找一个json库啦!首先把代码clone下来。源码集成进项目中

将include里的json文件夹拷贝到../app/src/main/cpp目录下。将src/lib_json里面的文件除去 CMakeLists.txt拷贝到../app/src/main/cpp目录下。最终如下:

cmake 怎么用

修改../app/src/main/cpp/CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.4.1)

add_library(

native_hello

SHARED

json_tool.h

json_reader.cpp

json_valueiterator.inl

json_value.cpp

json_writer.cpp

version.h.in

# 下面的cpp若是没有则新建一个,本文基于上篇文章

native_hello.cpp

)

target_link_libraries(

native_hello

android

log

)

make build一下

cmake 怎么用

What Are you!!!出错了,告诉在json_tool.h的第10行出错了,那行吧,点进去瞅一眼,如下:

cmake 怎么用

哦,include错了,应该改为 #include “json/config.h” 那就改吧!cv过来的所有文件都得去查看一下(试想一下,若是cv过来的文件有1W个,咋办…,改完这个,将来别的地方在引用,又忘了那个曾经是改过的了,停!stop,我眼疼!)。

编写测试代码打开../app/src/main/cpp/native_hello.cpp 更改如下:

//

// Created by xong on 2018/9/28.

//

#include<jni.h>

#include "json/json.h"

# define XONGFUNC(name)Java_com_xong_andcmake_jni_##name

extern "C" JNIEXPORT

jstring JNICALL

XONGFUNC(NativeFun_outputJsonCode)(JNIEnv *env, jclass thiz,

jstring jname, jstring jage, jstring jsex, jstring jtype)

{

Json::Value root;

constchar*name = env->GetStringUTFChars(jname, NULL);

constchar*age = env->GetStringUTFChars(jage, NULL);

constchar*sex = env->GetStringUTFChars(jsex, NULL);

constchar*type = env->GetStringUTFChars(jtype, NULL);

root[ "name"] = name;

root[ "age"] = age;

root[ "sex"] = sex;

root[ "type"] = type;

env->ReleaseStringUTFChars(jname, name);

env->ReleaseStringUTFChars(jage, age);

env->ReleaseStringUTFChars(jsex, sex);

env->ReleaseStringUTFChars(jtype, type);

returnenv->NewStringUTF(root.toStyledString().c_str());

}

extern "C"JNIEXPO原由网RT

jstring JNICALL

XONGFUNC(NativeFun_parseJsonCode)(JNIEnv *env, jclass thiz,

jstring jjson)

{

constchar*json_str = env->GetStringUTFChars(jjson, NULL);

std::string out_str;

Json::CharReaderBuilder b;

Json::CharReader *reader(b.newCharReader());

Json::Value root;

JSONCPP_STRING errs;

bool ok = reader->parse(json_str, json_str + std::strlen(json_str), &root, &errs);

if(ok && errs.size() == 0) {

std::string name = root[ "name"].asString();

std::string age = root[ "age"].asString();

std::string sex = root[ "sex"].asString();

std::string type = root[ "type"].asString();

out_str = "name: "+ name + "nage: "+ age + "nsex:"+ sex + "ntype: "+ type + "n";

}

env->ReleaseStringUTFChars(jjson, json_str);

returnenv->NewStringUTF(out_str.c_str());

}

修改NativeFun类如下

packagecom.xong.andcmake.jni;

/**

* Create by xong on 2018/9/28

*/

publicclassNativeFun{

static{

System.loadLibrary( "native_hello");

}

publicstaticnativeString outputJsonCode(String name, String age, String sex, String type);

publicstaticnativeString parseJsonCode(String json_str);

}

测试

packagecom.xong.andcmake;

importandroid.support.v7.app.AppCompatActivity;

importandroid.os.Bundle;

importandroid.widget.TextView;

importcom.xong.andcmake.jni.NativeFun;

publicclassMainActivityextendsAppCompatActivity{

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

TextView tv_native_content = findViewById(R.id.tv_native_content);

String outPutJson = NativeFun.outputJsonCode( "xong", "21", "man", "code");

String parseJson = NativeFun.parseJsonCode(outPutJson);

tv_native_content.setText( "生成的Json:n"+ outPutJson + "n解析:"+ parseJson);

}

}

结果如下图

cmake 怎么用

编译成库文件

OK,集成成功,但是太复杂,太麻烦了,每次要写那么一堆配置文件,少一个都不行,还要挨个的去改 include 想想都可怕,那么可不可以把这个jsoncpp打成库呢?那么我们就要考虑如下:

  1. 必须使用CMake,不使用编写mk的方式;
  2. 在任何系统上都可以,不可在编译库的时候切换到其他系统;

好吧,基于以上两点,百度搜了一波,发现…GG,没有符合的哎,用CMake就得去Linux下面,且需要自己构建工具链,要不然就是…mk…。再去Google搜一搜,发现还是这样的,难道,不存在?再去GitHub搜,发现没有相关的,无可奈何,去Google提供的sample中找一找吧,哈!还真有发现,链接:hello-libs

https://github.com/googlesamples/android-ndk/tree/master/hello-libs

编译so动态库

修改cpp目录为

cmake 怎么用

修改../cpp/jsoncpp/目录中的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(

# 库名字

jsoncpp

# 库类型

SHARED

# 库包含的资源

src/json_tool.h

src/json_reader.cpp

src/json_valueiterator.inl

src/json_value.cpp

src/json_writer.cpp

src/version.h.in)

# 导出目录 此处的设置 导出主目录在 Project/export文件夹内。

set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)

set_target_properties(

# 库名字

jsoncpp

# 设置输出.so动态库的路径

PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}")

add_custom_command(

# POST_BUILD 处 有三个值可选

# 分别是:

# PRE_BUILD:在 hello 运行其他规则前执行

# PRE_LINK:在编译源文件之后但在 链接其他二进制文件 或 运行静态库的库管理器 或 归档工具 之前执行

# POST_BUILD:最后执行

TARGET jsoncpp POST_BUILD

# 拷贝命令 将 ${CMAKE_CURRENT_SOURCE_DIR}/src/json/allocator.h 文件拷贝到 ${export_dir}/libsojsoncpp/include/json/ 文件夹内 且名字和之前的相同

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/allocator.h""${export_dir}/libsojsoncpp/include/json/allocator.h原由网"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/config.h""${export_dir}/libsojsoncpp/include/json/config.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/forwards.h""${export_dir}/libsojsoncpp/include/json/forwards.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/features.h""${export_dir}/libsojsoncpp/include/json/features.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/value.h""${export_dir}/libsojsoncpp/include/json/value.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/reader.h""${export_dir}/libsojsoncpp/include/json/reader.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/writer.h""${export_dir}/libsojsoncpp/include/json/writer.h"

COMMAND "${CMAKE_COMMAND}"-E copy "原由网${CMAKE_CURRENT_SOURCE_DIR}/src/json/assertions.h""${export_dir}/libsojsoncpp/include/json/assertions.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/autolink.h""${export_dir}/libsojsoncpp/include/json/autolink.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/json.h""${export_dir}/libsojsoncpp/include/json/json.h"

COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/version.h""${export_dir}/libsojsoncpp/include/json/version.h"

)

修改 ../cpp/CMakeLists.txt如下

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

# 设置资源主目录 CMAKE_CURRENT_SOURCE_DIR 代表当前CMakeLists.txt 所在的目录

set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})

# 设置CMake编译后文件的存放的临时目录

set(lib_build_DIR $ENV{HOME}/tmp)

# 得到 lib_build_DIR 文件夹内文件

file(MAKE_DIRECTORY ${lib_build_DIR})

# 添加子目录

add_subdirectory(${lib_src_DIR}/jsoncpp ${lib_build_DIR}/jsoncpp)

修改 ../app/build.gradle如下

apply plugin: 'com.android.application'

android {

...

defaultConfig {

...

externalNativeBuild {

cmake {

// 这里的名字最好和 ../cpp/jsoncpp/CMakeLists.txt 中设置的名字相同

targets 'jsoncpp'

}

...

}

}

...

externalNativeBuild {

cmake {

path 'src/main/cpp/CMakeLists.txt'

}

}

}

说明:点击Build/Make Project(或者 Make Module 'app') 会在 项目根目录下 新建 export 文件夹 在里面会存放 库所需的头文件和so动态库。

编译后如下

cmake 怎么用

so和头文件都生成了,但是我们在写../cpp/jsoncpp/CMakeLists.txt 文件时,也发现了,将所需的头文件导出到指定目录时有点儿费劲,只是名字换了下,若是用代码来写的话,一个for循环就可以了,那么在CMakeLists.txt中,可不可以实现类似的呢?哈哈,那当然是肯定的了,最终修改如下:

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(

jsoncpp

SHARED

src/json_tool.h

src/json_reader.cpp

src/json_valueiterator.inl

src/json_value.cpp

src/json_writer.cpp

src/version.h.in)

set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)

set_target_properties(

jsoncpp

PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}")

add_custom_command(

TARGET jsoncpp POST_BUILD

# 将 ${CMAKE_CURRENT_SOURCE_DIR}/src/json 文件夹下的文件 导出到 ${export_dir}/libajsoncpp/include/json/ 文件夹内

COMMAND "${CMAKE_COMMAND}"-E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/json""${export_dir}/libsojsoncpp/include/json/")

将之前生成的export文件夹删除,重新build发现是可以的。OK,动态库可以编译成功,那么讲道理,静态库也是一样的,来尝试下编译静态库库。

编译a静态库

在以上编译so动态库的前提下,修改../cpp/jsoncpp/CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(

jsoncpp

# 将 库 类型 由 SHARED 修改为 STATIC

STATIC

src/json_tool.h

src/json_reader.cpp

src/json_valueiterator.inl

src/json_value.cpp

src/json_writer.cpp

src/version.h.in)

set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)

set_target_properties(

jsoncpp

# 将 LIBRARY_OUTPUT_DIRECTORY 修改为 ARCHIVE_OUTPUT_DIRECTORY

# 方便查看 生成的a文件目录修改一下 即 将 libsojsoncpp 修改为 libajsoncpp

PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${export_dir}/libajsoncpp/lib/${ANDROID_ABI}")

add_custom_command(

TARGET jsoncpp POST_BUILD

COMMAND "${CMAKE_COMMAND}"-E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/json""${export_dir}/libajsoncpp/include/json/"

)

修改完成后,Build/Make Project(或者 Make Module 'app') 会在 Project/export目录下生成:

cmake 怎么用

这样编译.a静态库和.so动态库就完成了。

将so动态库、a静态库、以及对应的头文件,集中到一个文件夹中,本文因为是基于上篇的,那么这些文件就放在了,如下图:

cmake 怎么用

都放在了Project/export文件夹中,且在里面将so和a分开,分别放在了,libajsoncpp和libsojsoncpp文件夹中,在每个文件夹中,又有include文件夹来放库所需要的头文件,lib中放so以及a库文件。

链接so动态库

我们首先来链接我们较为熟悉的so动态库,然后再来链接a静态库。

  • 准备工作
  1. 将../app/src/main/cpp文件夹中的jsoncpp文件夹删除,以防我们用的不是库,而是…源码了(针对按着第二篇 将 源码拷贝到项目中的同学)。
  2. 将 ../app/src/main/cpp文件夹下的CMakeLists.txt内所有内容删除,以防和本文的CMakeLists.txt中的配置不同。
  3. 将 ../app/build.gradle 修改如下:

apply plugin: 'com.android.application'

android {

...

defaultConfig {

...

externalNativeBuild {

cmake {

arguments '-DANDROID_STL=c++_static'

}

}

}

buildTypes {

...

}

sourceSets {

main {

// 根据实际情况具体设置,由于本项目的lib放在 project/export/libsojsoncpp/lib 中 故如此设置

jniLibs.srcDirs = [ '../export/libsojsoncpp/lib']

}

}

externalNativeBuild {

cmake {

path 'src/main/cpp/CMakeLists.txt'

}

}

}

...

  • 写app/src/main/cpp/CMakeLists.txt文件

cmake_minimum_required(VERSION 3.4.1)

# 设置变量 找到存放资源的目录, ".."代表上一级目录

set(export_dir ${CMAKE_SOURCE_DIR}/../../../../export)

# 添加.so动态库(jsoncpp)

add_library(lib_so_jsoncpp SHARED IMPORTED)

# 链接

set_target_properties(

# 库名字

lib_so_jsoncpp

# 库所在的目录

PROPERTIES IMPORTED_LOCATION ${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}/libjsoncpp.so)

add_library(

native_hello

SHARED

native_hello.cpp

)

# 链接头文件

target_include_directories(

native_hello

PRIVATE

# native_hello 需要的头文件

${export_dir}/libsojsoncpp/include

)

# 链接项目中

target_link_libraries(

native_hello

android

log

# 链接 jsoncpp.so

lib_so_jsoncpp

)

嗯,这次看起来配置较多了,但是…,别慌 别慌 问题不大.jpg(自行脑部表情包) 我们来一条一条的看。最后,Build/Make Module 'app'.

cmake_minimum_required(VERSION 3.4.1) 这个就不用解释了吧,就是设置下CMake的最小版本而已。

set(....) 因为考虑到用到export 文件夹的次数较多,而且都是绝对路径,所以就来设置一个变量来简化啦。export_dir 就是变量的名字,${CMAKE_SOURCE_DIR} 是获取当前CMakeLists.txt 所在的路径,然后 一路 "../"去找到 我们存放资源文件的 export 文件夹。

add_library(lib_so_jsoncpp SHARED IMPORTED) 这个见名之意啦,就是添加库文件,后面的三个参数 "lib_so_jsoncpp" 库名字;"SHARED" 因为我们要导入 so 动态库,所以就是 SHARED 了; "IMPORTED" 然后导入;

set_target_properties 接下来就是这句了,后面的参数较多,较长,就不拷贝到这里了。我们在 上句 已经添加库了,但是…库是空的呀(注意后面是 imported),什么都没有,只是一个名字 + 类型,所以接下来就得需要它来将名字和真实的库链接起来,我已经在上面的CMakeLists.txt中写上注释了,这里只说下在前面没有提到过的"${ANDROID_ABI}",这是啥?上面的语句将此拼接到了里面,但是我真实的路径中没有这个文件夹呀,去看下../libsojsoncpp/lib/下是啥,如下:

cmake 怎么用

嗯啦,就是那一堆架构,所以…这个值就代表这些了(默认,全部类型)。

  1. 然后接下来就又是一个add_library 但是这个是带资源的了。没啥好说的了.
  2. target_include_directories 我们有库了,但是没有对应的头文件咋行,所以这句就是链接库对应的头文件了。
  3. target_link_libraries 最后将所需的头文件,链接到项目中就可以啦!
  • 调用代码

cpp层的代码其实是不用改,直接用我们上次 拷贝 源码的方式就行,但是为了方便直接看本篇的同学,还是贴下 native_hello.cpp 内的代码如下:

//

// Created by xong on 2018/9/28.

//

#include<jni.h>

#include <string>

#include "json/json.h"

# define XONGFUNC(name)Java_com_xong_andcmake_jni_##name

extern "C" JNIEXPORT

jstring JNICALL

XONGFUNC(NativeFun_outputJsonCode)(JNIEnv *env, jclass thiz,

jstring jname, jstring jage, jstring jsex, jstring jtype)

{

Json::Value root;

constchar*name = env->GetStringUTFChars(jname, NULL);

constchar*age = env->GetStringUTFChars(jage, NULL);

constchar*sex = env->GetStringUTFChars(jsex, NULL);

constchar*type = env->GetStringUTFChars(jtype, NULL);

root[ "name"] = name;

root[ "age"] = age;

root[ "sex"] = sex;

root[ "type"] = type;

env->ReleaseStringUTFChars(jname, name);

env->ReleaseStringUTFChars(jage, age);

env->ReleaseStringUTFChars(jsex, sex);

env->ReleaseStringUTFChars(jtype, type);

returnenv->NewStringUTF(root.toStyledString().c_str());

}

extern "C"JNIEXPORT

jstring JNICALL

XONGFUNC(NativeFun_parseJsonCode)(JNIEnv *env, jclass thiz,

jstring jjson)

{

constchar*json_str = env->GetStringUTFChars(jjson, NULL);

std::string out_str;

Json::CharReaderBuilder b;

Json::CharReader *reader(b.newCharReader());

Json::Value root;

JSONCPP_STRING errs;

bool ok = reader->parse(json_str, json_str + std::strlen(json_str), &root, &errs);

if(ok && errs.size() == 0) {

std::string name = root[ "name"].asString();

std::string age = root[ "age"].asString();

std::string sex = root[ "sex"].asString();

std::string type = root[ "type"].asString();

out_str = "name: "+ name + "nage: "+ age + "nsex:"+ sex + "ntype: "+ type + "n";

}

env->ReleaseStringUTFChars(jjson, json_str);

returnenv->NewStringUTF(out_str.c_str());

}

对应的Java层代码如下

packagecom.xong.andcmake.jni;

/**

* Create by xong on 2018/9/28

*/

publicclassNativeFun{

static{

System.loadLibrary( "native_hello");

}

publicstaticnativeString outputJsonCode(String name, String age, String sex, String type);

publicstaticnativeString parseJsonCode(String json_str);

}

调用代码

packagecom.xong.andcmake;

importandroid.support.v7.app.AppCompatActivity;

importandroid.os.Bundle;

importandroid.widget.TextView;

importcom.xong.andcmake.jni.NativeFun;

publicclassMainActivityextendsAppCompatActivity{

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

TextView tv_native_content = findViewById(R.id.tv_native_content);

String outPutJson = NativeFun.outputJsonCode( "xong", "21", "man", "so");

String parseJson = NativeFun.parseJsonCode(outPutJson);

tv_native_content.setText( "生成的Json:n"+ outPutJson + "n解析:"+ parseJson);

}

}

  • 结果

cmake 怎么用

嗯!集成成功,那么下面我们来集成下a静态库。链接a静态库

我们还是基于上面链接so动态库的修改。

  • 首先修改 ../app/build.gradle 文件如下

apply plugin: 'com.android.application'

android {

...

defaultConfig {

...

externalNativeBuild {

cmake {

arguments '-DANDROID_STL=c++_static'

}

}

}

...

// 删除 或注释

// sourceSets {

// main {

// jniLibs.srcDirs = ['../export/libsojsoncpp/lib']

// }

// }

externalNativeBuild {

cmake {

path 'src/main/cpp/CMakeLists.txt'

}

}

}

只是将 集成 so时添加的 sourceSets 标签删除(或注释啦!)。

  • 其次修改 ../app/main/src/cpp/CMakeLists.txt 如下

cmake_minimum_required(VERSION 3.4.1)

# 设置变量 找到存放资源的目录, ".."代表上一级目录

set(export_dir ${CMAKE_SOURCE_DIR}/../../../../export)

# 添加.so动态库(jsoncpp)

# add_library(lib_so_jsoncpp SHARED IMPORTED)

add_library(lib_a_jsoncpp STATIC IMPORTED)

# 链接

#set_target_properties(

# lib_so_jsoncpp

# PROPERTIES IMPORTED_LOCATION ${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}/libjsoncpp.so)

set_target_properties(

lib_a_jsoncpp

PROPERTIES IMPORTED_LOCATION ${export_dir}/libajsoncpp/lib/${ANDROID_ABI}/libjsoncpp.a)

add_library(

native_hello

SHARED

native_hello.cpp

)

# 链接头文件

#target_include_directories(

# native_hello

# PRIVATE

# # native_hello 需要的头文件

# ${export_dir}/libsojsoncpp/include

#)

target_include_directories(

native_hello

PRIVATE

# native_hello 需要的头文件

${export_dir}/libajsoncpp/include

)

# 链接项目中

target_link_libraries(

native_hello

android

log

# 链接 jsoncpp.so

# lib_so_jsoncpp

lib_a_jsoncpp

)

在上个集成so的配置上修改,如上,修改的地方都一 一 对应好了,基本上和集成so没区别。

  • 调用

Java层不需修改,调用时传的参数如下:

packagecom.xong.andcmake;

importandroid.support.v7.app.AppCompatActivity;

importandroid.os.Bundle;

importandroid.widget.TextView;

importcom.xong.andcmake.jni.NativeFun;

publicclassMainActivityextendsAppCompatActivity{

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

TextView tv_native_content = findViewById(R.id.tv_native_content);

String outPutJson = NativeFun.outputJsonCode( "xong", "21", "man", "a");

String parseJson = NativeFun.parseJsonCode(outPutJson);

tv_native_content.setText( "生成的Json:n"+ outPutJson + "n解析:"+ parseJson);

}

}

  • 结果

cmake 怎么用

可以看到type变成了 "a",这样的话,静态库也就算集成成功了。

动态库和静态库的区别

有的人会说了,你这都用的json,而且返回 type 是你传进去的呀,你就是集成 so 传 a 那么就算集成a了?

另一个我们怎么会知道打到apk中的是so动态库,还是a静态库呢?不是都说了么,Android中只支持调用so动态库,不支持a静态库的,那么这…集成a静态库这不是扯淡么?

OK,接下来就来解释这一系列的问题,首先我们要知道什么是静态库什么又是动态库。

参考Linux下的库,地址如下所示:

https://blog.csdn.net/llzk_/article/details/55519242

抽取出主要的:

  • 静态库

链接时间: 静态库的代码是在编译过程中被载入程序中;

链接方式: 目标代码用到库内什么函数,就去将该函数相关的数据整合进目标代码;

优点: 是在编译后的执行程序不在需要外部的函数库支持;

缺点: 如果所使用的静态库发生更新改变,程序必须重新编译。

  • 动态库

链接时间: 动态库在编译的时候并没有被编译进目标代码,而是在运行时用到该库中函数时才去调用;

链接方式: 动态链接,用到该库内函数时就去加载该库;

优点: 动态库的改变并不影响程序,即不需要重新编译;

缺点: 因为函数库并没有整合进程序,所以程序的运行环境必须依赖该库文件。

再精简一点:

静态库是一堆cpp文件,每次都需要编译才能运行,在自己的代码中用到哪个,就从这一堆cpp中取出自己需要的进行编译;

动态库是将一堆cpp文件都编译好了,运行的时候不会对cpp进行重新编译,当需要到库中的某个函数时,就会将库加载进来,不需要时就不用加载,前提,这个库必须存在。

所以,就可以回答上述的疑问了,对,没错Android的确是只能调用so动态库,我们的集成的a静态库,用到静态库中的函数时,就会去静态库中拿对应的元数据,然后将这些数据再打入到打入到我们的最终要调用的so动态库中,在上述中就是native_hello.so了。

然后我们在集成so动态库时在../app/build.gradle 中加了一个标签哈,如下:

sourceSets {

main {

jniLibs.srcDirs = [ '../export/libsojsoncpp/lib']

}

}

经过上面的解释,再来理解这句就不难了,上面已经说过了:

当需要到库中的某个函数时,就会将库加载进来,不需要时就不用加载,前提,这个库必须存在。

所以啦,native_hello.so依赖于jsoncpp.so,即jsoncpp.so必须存在,那么加这个的意思就是,将jsoncpp.so打入apk中。我们可以将上面的集成so动态库的apk用 jadx 查看一下,如下:

cmake 怎么用

上面的结论没错咯,里面确实有两个so,一个jsoncpp.so另一个就是我们自己的native_hello.so;

那么我们再来看一下集成a静态库的吧!如下:

cmake 怎么用

这就是区别啦!

总结

so方式,只要你代码中涉及了,那么它就要存在,即使你只是调用,后续不使用它,它也存在。

a方式,只需要在编码过程中,保持它存在就好,用几个函数,就从a中取几个函数。

本来想将这一篇分成三篇来写的,想了又想,Android开发嘛,没必要对native很那么了解,所以就压缩压缩,压缩成一篇了。在这三篇中,我们发现,写CMakeLists.txt也不是那么很麻烦,而且很多都是重复的,都是可以用脚本来生成的,比如 add_library 中添加的资源文件,当然其他的也一样啦,那么这个 CMakeLists.txt 是不是可以写一个小脚本呢?我感觉可以。另一个,如何用Camke构建a静态库、so动态库,以及如何集成,在Google的sample中都有的,再贴一下链接:android_ndk

https://github.com/googlesamples/android-ndk

而且添加的时间也挺长的了,但是,百度到的文章还是 5年前的,真的是…不知道说啥了,还是多看些Google Github比较好。哈哈哈哈~

最后,上述三篇文章中涉及的源码均已上传到GitHub,链接:UseCmakeBuildLib

https://github.com/lilinxiong/UseCmakeBuildLib

内容版权声明:除非注明原创否则皆为转载,再次转载请注明出处。

文章标题: cmake 怎么用

文章地址: www.58yuanyou.com/jiqiao/227363.html

相关推荐