本篇基于上篇构建好的a静态库和so动态库,若本身有a或so那么能够直接看本篇了,若没有那么建议先去看上篇----如何将现有的cpp代码集成到项目中java
将so动态库、a静态库、以及对应的头文件,集中到一个文件夹中,本文由于是基于上篇的,那么这些文件就放在了,以下图:android
都放在了Project/export文件夹中,且在里面将so和a分开,分别放在了,libajsoncpp和libsojsoncpp文件夹中,在每一个文件夹中,又有include文件夹来放库所须要的头文件,lib中放so以及a库文件。c++
咱们首先来连接咱们较为熟悉的so动态库,而后再来连接a静态库。git
准备工做github
将../app/src/main/cpp文件夹中的jsoncpp文件夹删除,以防咱们用的不是库,而是…源码了(针对按着第二篇 将 源码拷贝到项目中的同窗)。json
将 ../app/src/main/cpp文件夹下的CMakeLists.txt内全部内容删除,以防和本文的CMakeLists.txt中的配置不一样。微信
将 ../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文件app
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(自行脑部表情包) 咱们来一条一条的看ide
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/下是啥,以下:
嗯啦,就是那一堆架构,因此…这个值就表明这些了(默认,所有类型)。
而后接下来就又是一个add_library 可是这个是带资源的了。没啥好说的了.
target_include_directories 咱们有库了,可是没有对应的头文件咋行,因此这句就是连接库对应的头文件了。
target_link_libraries 最后将所需的头文件,连接到项目中就能够啦!
最后,Build/Make Module 'app'.
调用代码
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;
const char *name = env->GetStringUTFChars(jname, NULL);
const char *age = env->GetStringUTFChars(jage, NULL);
const char *sex = env->GetStringUTFChars(jsex, NULL);
const char *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);
return env->NewStringUTF(root.toStyledString().c_str());
}
extern "C" JNIEXPORT
jstring JNICALL
XONGFUNC(NativeFun_parseJsonCode)(JNIEnv *env, jclass thiz,
jstring jjson)
{
const char *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);
return env->NewStringUTF(out_str.c_str());
}
复制代码
对应的Java层代码以下:
package com.xong.andcmake.jni;
/** * Create by xong on 2018/9/28 */
public class NativeFun {
static {
System.loadLibrary("native_hello");
}
public static native String outputJsonCode(String name, String age, String sex, String type);
public static native String parseJsonCode(String json_str);
}
复制代码
调用代码:
package com.xong.andcmake;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.xong.andcmake.jni.NativeFun;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(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);
}
}
复制代码
结果:
嗯!集成成功,那么下面咱们来集成下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层不需修改,调用时传的参数以下:
package com.xong.andcmake;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.xong.andcmake.jni.NativeFun;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(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);
}
}
复制代码
结果:
能够看到type变成了 "a",这样的话,静态库也就算集成成功了。
有的人会说了,你这都用的json,并且返回 type 是你传进去的呀,你就是集成 so 传 a 那么就算集成a了?
另外一个咱们怎么会知道打到apk中的是so动态库,仍是a静态库呢?不是都说了么,Android中只支持调用so动态库,不支持a静态库的,那么这…集成a静态库这不是扯淡么?
OK,接下来就来解释这一系列的问题,首先咱们要知道什么是静态库什么又是动态库。
参考Linux下的库
抽取出主要的:
静态库
连接时间: 静态库的代码是在编译过程当中被载入程序中;
连接方式: 目标代码用到库内什么函数,就去将该函数相关的数据整合进目标代码;
优势: 是在编译后的执行程序不在须要外部的函数库支持;
缺点: 若是所使用的静态库发生更新改变,程序必须从新编译。
动态库
连接时间: 动态库在编译的时候并无被编译进目标代码,而是在运行时用到该库中函数时才去调用;
连接方式: 动态连接,用到该库内函数时就去加载该库;
优势: 动态库的改变并不影响程序,即不须要从新编译;
缺点: 由于函数库并无整合进程序,因此程序的运行环境必须依赖该库文件。
再精简一点:
静态库是一堆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 查看一下,以下:
上面的结论没错咯,里面确实有两个so,一个jsoncpp.so另外一个就是咱们本身的native_hello.so;
那么咱们再来看一下集成a静态库的吧!以下:
这就是区别啦!
so方式,只要你代码中涉及了,那么它就要存在,即便你只是调用,后续不使用它,它也存在。
a方式,只须要在编码过程当中,保持它存在就好,用几个函数,就从a中取几个函数。
jstring name = "xong";
const char* ccp_name = env->GetStringUTFChars(name, NULL);
env->ReleaseStringUTFChars(name, ccp_name);
复制代码
现象:
写JNI时这两个不陌生吧,不少人都会说,用了GetStringUTFChars 必须 调用 ReleaseStringUTFChars 讲资源释放掉,但 调用完ReleaseStringUTFChars会发现 ccp_name 还能够访问,即并无释放掉资源。
问题:
欢迎在下方的评论进行探讨。
原本想将这一篇分红三篇来写的,想了又想,Android开发嘛,不必对native很那么了解,因此就压缩压缩,压缩成一篇了。
在这三篇中,咱们发现,写CMakeLists.txt也不是那么很麻烦,并且不少都是重复的,都是能够用脚原本生成的,好比 add_library 中添加的资源文件,固然其余的也同样啦,那么这个 CMakeLists.txt 是否是能够写一个小脚本呢?我感受能够。
另外一个,如何用Camke构建a静态库、so动态库,以及如何集成,在Google的sample中都有的,再贴一下连接:android_ndk , 并且添加的时间也挺长的了,可是,百度到的文章仍是 5年前的,真的是…不知道说啥了,仍是多看些Google Github比较好。哈哈哈哈~
最后,上述三篇文章中涉及的源码均已上传到GitHub,连接:UseCmakeBuildLib
END