关于在Android中使用CMake你所需要了解的一切(三)
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
本篇基于上篇构建好的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_helloSHAREDnative_hello.cpp )# 链接头文件 target_include_directories(native_helloPRIVATE# native_hello 需要的头文件${export_dir}/libsojsoncpp/include )# 链接项目中 target_link_libraries(native_helloandroidlog# 链接 jsoncpp.solib_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_##nameextern "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 {@Overrideprotected 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_jsoncppPROPERTIES IMPORTED_LOCATION ${export_dir}/libajsoncpp/lib/${ANDROID_ABI}/libjsoncpp.a)add_library(native_helloSHAREDnative_hello.cpp )# 链接头文件 #target_include_directories( # native_hello # PRIVATE # # native_hello 需要的头文件 # ${export_dir}/libsojsoncpp/include #) target_include_directories(native_helloPRIVATE# native_hello 需要的头文件${export_dir}/libajsoncpp/include )# 链接项目中 target_link_libraries(native_helloandroidlog# 链接 jsoncpp.so # lib_so_jsoncpplib_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 {@Overrideprotected 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
转载于:https://juejin.im/post/5bb028575188255c60042c8d
关于在Android中使用CMake你所需要了解的一切(三)相关推荐
- 关于在Android中使用CMake你所需要了解的一切(一)
相信大家在开发的过程中,都或多或少的接触过JNI,然后每次要接触JNI的时候,倒吸一口冷气,太难啦! 只有Java代码和C++代码 还好,在新建项目的时候把那个 "Include C ...
- 关于在Android中使用CMake你所需要了解的一切(二)
虽然本篇和上篇没很大的关系,但-还是建议先去看下上篇----初次使用CMake构建native项目 初次使用CMake构建native项目 如何将现有的cpp代码集成到项目中 拷贝源码 编译成库文件 ...
- 浅谈android中图片处理之色彩特效处理ColorMatrix(三)
在android开发中对图片处理很是频繁,其中对图片的颜色处理就是很常见的一种.我们经常看到一些类似美图秀秀,美颜相机的app,为什么那么黑的人拍出来是确实那么地白呢?长的那么那个(丑)的人,用美颜相 ...
- Android studio 使用Cmake完成C/C++ 的使用以及生成so文件
今天,简单讲讲android中关于Cmake进行NDK编程的相关知识. Android studio 2.2版本以后对C/C++的支持可以说很方便了,当然官方推荐使用Cmake完成对C/C++的支持 ...
- 【Android 逆向】Android 中常用的 so 动态库 ( /system/lib/libc.so 动态库 | libc++.so 动态库 | libstdc++.so 动态库 )
文章目录 一.拷贝并分析 Android 中的 /system/lib/libc.so 动态库 二.拷贝并分析 Android 中的 /system/lib/libc++.so 动态库 三.拷贝并分析 ...
- Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)
在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>> ,简单的阐述 了 Android View 绘制流程的三个步骤,即: 1. ...
- Android中的Touch事件
Android中的Touch事件处理 主要内容 Activity或View类的onTouchEvent()回调函数会接收到touch事件. 一个完整的手势是从ACTION_DOWN开始,到ACTION ...
- Android 中使用地图加载wms服务(高德地图,谷歌地图,天地图)
转载请注明出处:http://blog.csdn.net/zkjthinking/article/details/77278838 由于公司需求需要在移动平台上加载自己发布的wms 服务: 高德地图加 ...
- Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发
Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...
最新文章
- 移位运算[c][code]
- Servlet中使用getInputStream进行文件上传
- 想学好Java开发,你要做到这三点
- OpenCV Viz转变
- leetcode 334. Increasing Triplet Subsequence | 334. 递增的三元子序列(一种较trick的解法)
- scala 加载与保存xml文档
- VS.net中的远程调试
- 3-07. 求前缀表达式的值(25) (ZJU_PAT数学)
- ​【文末有福利】《信条》中的物理学-时间机器存在吗?
- 【note】Java程序设计基础第五版(下)
- mysql5.5java安装_配置非安装版的mysql 5.5
- 微软Azure云计算服务主导全球
- mysql的varchar与text对比
- pytorch实现resnet网络结构
- 半小时一篇文过完C语言基础知识点
- GBase 8a管理集群gcware的日志-vote leader、flower、candidate部分
- 关于2018后新款 Mac增加T2安全芯片造成无法U盘启动解决办法
- shell检测硬件状态脚本
- 2015美国大学计算机科学专业排名,2015年US News美国大学计算机专业排名
- Linux非交互式修改用户密码
热门文章
- 驻云科技:上海云栖大会原力觉醒
- CTFHub | PHPINFO
- Python一炮句搞定网页登录验证码自动输入
- 软件激活的两种方式原理
- CSS line-hight ,vertical-align,父元素高度以及行框,行内框的关系
- 考c语言是什么,C语言是计算机二级吗考什么?考了有什么用
- php 502 bad gateway,解决lnmp服务器环境访问php文件出现502 Bad Gateway错误问题
- 项目四:linux QT WIFI连接工具
- html通过jsp连接sql数据库数据,jsp连接sql Server数据库教程(示例代码)
- 汕头网络货运平台等保三级的技术要求有哪些呢 甜甜告诉您