使用cmake解决Android中对第三方库的依赖

现在Android studio已经支持使用cmake来进行native开发了。比起ndk来说,cmake好用多了,比如可以在Android studio里面直接dubug native代码,对native 代码也支持高亮显示、代码提示、快捷跳转和自动格式化等功能。总得来说使用Android studio开发c/c++现在几乎和开发java一样方便了。

现在假设我们有这样一个需求:我们需要依赖一个第三方的so库;我们需要提供给客户我们打好包后的so库;我们还需要创建我们自己的jni库来进行测试,要怎么进行配置呢?

首先我们创建一个工程,记得选中”include c++ support”,这样Android studio就会自动帮我们创建好CMakeLists.txt文件了,这个文件就是对native的依赖、编译等进行配置的文件。
接着我们新建一个module layer1,这个module用来生成一个so库作为第三方的sdk。创建接口文件Layer.h,在接口我们定义了两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// Created by Li Yanshun on 2017/6/6.
//

#ifndef SINGLELIB_LAYER1_H
#define SINGLELIB_LAYER1_H

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

#define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR,"Netclient",__VA_ARGS__)

#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
std::string (*getResponse)();

std::string (*getRequest)();

} NET_API_FUNCTIONS_TYPE_LAYER1;

extern __attribute__ ((visibility ("default"))) NET_API_FUNCTIONS_TYPE_LAYER1 net_client_layer1;

#ifdef __cplusplus
}
#endif

#endif //SINGLELIB_LAYER1_H

创建文件Layer.cpp来实现这个接口,每个方法都返回一个对应的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
// Created by Li Yanshun on 2017/6/6.
//

#include "Layer1.h"

std::string get_response() {
LOG_E("layer1 get response");
return "layer1:Response";
}

std::string get_request() {
LOG_E("layer1 get request");
return "layer1:Request";
}


__attribute__ ((visibility ("default"))) NET_API_FUNCTIONS_TYPE_LAYER1 net_client_layer1 = {
get_response,
get_request,
};

最后创建CmakeLists.txt来配置一下工程,通过add_library方法将我们源代码添加进来,生成的so文件命名为layer1,但是系统会自动在名字前面添加lib前缀。

1
2
3
4
5
6
7
8
9
10
11
12
13
cmake_minimum_required(VERSION 3.4.1)

include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp)
set(jnilibs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${jnilibs}/${ANDROID_ABI})

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti -lstdc++")

add_library(layer1 SHARED src/main/cpp/Layer1.cpp )

find_library(log-lib log )

target_link_libraries(layer1 ${log-lib} )

编译运行后,会在jniLibs目录下的对应ABI目录里生成liblayer1.so
回到app module来,现在我们有了第三方库,将其复制到app module的jniLibs目录下。在app module的cpp文件夹下创建include、layer2和layer3三个文件夹,layer2将是我们对外提供的sdk,layer3将会是我们用来测试的jni sdk。
定义Layer2.h 和Layer.cpp,跟layer1的基本类似,不同之处是我们会调用layer1的接口来生成字符串,并加上layer2的前缀

1
2
3
4
5
std::string get_response_layer2() {
LOG_E("layer2 get response");
const std::string &layer1 = net_client_layer1.getResponse();
return "layer2:" + layer1;
}

在layer2目录下创建一个新的CMakeLists.txt, 将liblayer1.so作为依赖引入进来,将Layer2.cpp作为源代码编译进来,最后用target_link_libraries
将layer2和layer1都链接起来。

1
2
3
4
5
6
add_library(layer1 SHARED IMPORTED )
set_target_properties(layer1 PROPERTIES IMPORTED_LOCATION "${jnilibs}/${ANDROID_ABI}/liblayer1.so")

add_library(layer2 SHARED Layer2.cpp)

target_link_libraries(layer2 layer1 log)

在layer3目录下创建Layer3.cpp,用来实现我们的jni接口,当然里面调用的是layer2的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

//
// Created by Li Yanshun on 2017/6/6.
//
#include <jni.h>
#include <string>
#include "../include/Layer2.h"

extern "C"
{
JNIEXPORT jstring JNICALL
Java_com_mushuichuan_cmakemutiso_MainActivity_requestStringFromJNI(JNIEnv *env, jobject) {

LOG_E("layer3 get request");
std::string request = "Layer3:" + net_client_layer2.getRequest();
return env->NewStringUTF(request.c_str());
}

JNIEXPORT jstring JNICALL
Java_com_mushuichuan_cmakemutiso_MainActivity_responseStringFromJNI(JNIEnv *env, jobject) {

LOG_E("layer3 get response");
std::string response = "Layer3:" + net_client_layer2.getResponse();
return env->NewStringUTF(response.c_str());
}
}

创建CMakeList.txt,将对layer2的依赖添加进来

1
2
3
4
5
6
7
8
add_library(layer3 SHARED Layer3.cpp)

add_library(layer2sdk SHARED IMPORTED )
set_target_properties(layer2sdk PROPERTIES IMPORTED_LOCATION "${jnilibs}/${ANDROID_ABI}/liblayer2.so")

find_library( log-lib log )

target_link_libraries(layer3 layer2 ${log-lib})

在app module的CMakeLists.txt里将layer2和layer3给添加进来

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.4.1)

include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)
set(jnilibs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${jnilibs}/${ANDROID_ABI})

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti -lstdc++")


ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/src/main/cpp/layer2)
ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/src/main/cpp/layer3)

编译运行,在jniLibs目录下对应的ABI里会生成liblayer2.so和liblayer3.so。在App调用对应的接口也会返回从layer1->layer2->layer3层层包装
过后的字符串。至此我们就满足了上面的需求了即依赖了第三方sdk,还创建我们自己的sdk,同时和可以通过jni接口来测试我们的sdk。

项目源码将Github