内容简介:本篇基于上篇构建好的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 编译,你需要了解什么
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Types and Programming Languages
Benjamin C. Pierce / The MIT Press / 2002-2-1 / USD 95.00
A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute. The study of typ......一起来看看 《Types and Programming Languages》 这本书的介绍吧!