内容简介:本篇基于上篇构建好的a静态库和so动态库,若自己有a或so那么可以直接看本篇了,若没有那么建议先去看上篇----将so动态库、a静态库、以及对应的头文件,集中到一个文件夹中,本文因为是基于上篇的,那么这些文件就放在了,如下图:都放在了Project/export文件夹中,且在里面将so和a分开,分别放在了,libajsoncpp和libsojsoncpp文件夹中,在每个文件夹中,又有include文件夹来放库所需要的头文件,lib中放so以及a库文件。
本篇基于上篇构建好的a静态库和so动态库,若自己有a或so那么可以直接看本篇了,若没有那么建议先去看上篇---- 如何将现有的cpp代码集成到项目中
- 初次使用CMake构建native项目
- 如何将现有的cpp代码集成到项目中
- 拷贝源码
- 编译成库文件
- CMake链接a静态库以及so动态库及动态库和静态库的区别
准备工作
将so动态库、a静态库、以及对应的头文件,集中到一个文件夹中,本文因为是基于上篇的,那么这些文件就放在了,如下图:
都放在了Project/export文件夹中,且在里面将so和a分开,分别放在了,libajsoncpp和libsojsoncpp文件夹中,在每个文件夹中,又有include文件夹来放库所需要的头文件,lib中放so以及a库文件。
链接so动态库
我们首先来链接我们较为熟悉的so动态库,然后再来链接a静态库。
-
准备工作
-
将../app/src/main/cpp文件夹中的jsoncpp文件夹删除,以防我们用的不是库,而是…源码了(针对按着第二篇 将 源码拷贝到项目中的同学)。
-
将 ../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文件
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(自行脑部表情包) 我们来一条一条的看
-
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静态库。
-
链接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中取几个函数。
question
jstring name = "xong"; const char* ccp_name = env->GetStringUTFChars(name, NULL); env->ReleaseStringUTFChars(name, ccp_name); 复制代码
现象:
写JNI时这两个不陌生吧,很多人都会说,用了 GetStringUTFChars 必须 调用 ReleaseStringUTFChars 讲资源释放掉,但 调用完 ReleaseStringUTFChars 会发现 ccp_name 还可以访问,即并没有释放掉资源。
问题:
- 只调用 GetStringUTFChars 不调用 ReleaseStringUTFChars 会不会造成内存泄漏,从而导致内存崩溃;
- 调用完 ReleaseStringUTFChars 后是否 应该 继续访问 ccp_name ;
- 应该在什么场合使用 ReleaseStringUTFChars ;
欢迎在下方的评论进行探讨。
other
本来想将这一篇分成三篇来写的,想了又想,Android开发嘛,没必要对native很那么了解,所以就压缩压缩,压缩成一篇了。
在这三篇中,我们发现,写CMakeLists.txt也不是那么很麻烦,而且很多都是重复的,都是可以用脚本来生成的,比如 add_library 中添加的资源文件,当然其他的也一样啦,那么这个 CMakeLists.txt 是不是可以写一个小脚本呢?我感觉可以。
另一个,如何用Camke构建a静态库、so动态库,以及如何集成,在Google的sample中都有的,再贴一下链接: android_ndk , 而且添加的时间也挺长的了,但是,百度到的文章还是 5年前的,真的是…不知道说啥了,还是多看些Google Github比较好。哈哈哈哈~
最后,上述三篇文章中涉及的源码均已上传到GitHub,链接: UseCmakeBuildLib
END
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 关于内存你需要了解的(二)
- 你需要了解的 nginx 基础配置
- 关于 CPU 你需要了解的(二)
- 前端也需要了解的 JSONP 安全
- UMI.js需要了解的知识
- 关于 Android 编译,你需要了解什么
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。