本篇文章已授权微信公众号 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静态库。

  1. 准备工作

    1. 将../app/src/main/cpp文件夹中的jsoncpp文件夹删除,以防我们用的不是库,而是…源码了(针对按着第二篇 将 源码拷贝到项目中的同学)。

    2. 将 ../app/src/main/cpp文件夹下的CMakeLists.txt内所有内容删除,以防和本文的CMakeLists.txt中的配置不同。

    3. 将 ../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'}}
      }
      ...
      复制代码
  2. 写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(自行脑部表情包) 我们来一条一条的看

    1. cmake_minimum_required(VERSION 3.4.1) 这个就不用解释了吧,就是设置下CMake的最小版本而已。

    2. set(....) 因为考虑到用到export 文件夹的次数较多,而且都是绝对路径,所以就来设置一个变量来简化啦。export_dir 就是变量的名字,${CMAKE_SOURCE_DIR} 是获取当前CMakeLists.txt 所在的路径,然后 一路 "../"去找到 我们存放资源文件的 export 文件夹。

    3. add_library(lib_so_jsoncpp SHARED IMPORTED) 这个见名之意啦,就是添加库文件,后面的三个参数 "lib_so_jsoncpp" 库名字;"SHARED" 因为我们要导入 so 动态库,所以就是 SHARED 了; "IMPORTED" 然后导入;

    4. set_target_properties 接下来就是这句了,后面的参数较多,较长,就不拷贝到这里了。我们在 上句 已经添加库了,但是…库是空的呀(注意后面是 imported),什么都没有,只是一个名字 + 类型,所以接下来就得需要它来将名字和真实的库链接起来,我已经在上面的CMakeLists.txt中写上注释了,这里只说下在前面没有提到过的"${ANDROID_ABI}",这是啥?上面的语句将此拼接到了里面,但是我真实的路径中没有这个文件夹呀,去看下../libsojsoncpp/lib/下是啥,如下:

      嗯啦,就是那一堆架构,所以…这个值就代表这些了(默认,全部类型)。

    5. 然后接下来就又是一个add_library 但是这个是带资源的了。没啥好说的了.

    6. target_include_directories 我们有库了,但是没有对应的头文件咋行,所以这句就是链接库对应的头文件了。

    7. target_link_libraries 最后将所需的头文件,链接到项目中就可以啦!

    最后,Build/Make Module 'app'.

  3. 调用代码

    1. 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());
      }
      复制代码
    2. 对应的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);
      }复制代码
    3. 调用代码:

      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);}
      }复制代码
    4. 结果:

      嗯!集成成功,那么下面我们来集成下a静态库。

链接a静态库

我们还是基于上面链接so动态库的修改。

  1. 首先修改 ../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 标签删除(或注释啦!)。

  2. 其次修改 ../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没区别。

  3. 调用

    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);}
    }复制代码
  4. 结果:

    可以看到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 还可以访问,即并没有释放掉资源。

问题:

  1. 只调用GetStringUTFChars不调用ReleaseStringUTFChars会不会造成内存泄漏,从而导致崩溃;
  2. 调用完ReleaseStringUTFChars后是否应该继续访问ccp_name;
  3. 应该在什么场合使用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你所需要了解的一切(三)相关推荐

  1. 关于在Android中使用CMake你所需要了解的一切(一)

    ​ 相信大家在开发的过程中,都或多或少的接触过JNI,然后每次要接触JNI的时候,倒吸一口冷气,太难啦! ​ 只有Java代码和C++代码 还好,在新建项目的时候把那个 "Include C ...

  2. 关于在Android中使用CMake你所需要了解的一切(二)

    虽然本篇和上篇没很大的关系,但-还是建议先去看下上篇----初次使用CMake构建native项目 初次使用CMake构建native项目 如何将现有的cpp代码集成到项目中 拷贝源码 编译成库文件 ...

  3. 浅谈android中图片处理之色彩特效处理ColorMatrix(三)

    在android开发中对图片处理很是频繁,其中对图片的颜色处理就是很常见的一种.我们经常看到一些类似美图秀秀,美颜相机的app,为什么那么黑的人拍出来是确实那么地白呢?长的那么那个(丑)的人,用美颜相 ...

  4. Android studio 使用Cmake完成C/C++ 的使用以及生成so文件

    今天,简单讲讲android中关于Cmake进行NDK编程的相关知识. Android studio 2.2版本以后对C/C++的支持可以说很方便了,当然官方推荐使用Cmake完成对C/C++的支持 ...

  5. 【Android 逆向】Android 中常用的 so 动态库 ( /system/lib/libc.so 动态库 | libc++.so 动态库 | libstdc++.so 动态库 )

    文章目录 一.拷贝并分析 Android 中的 /system/lib/libc.so 动态库 二.拷贝并分析 Android 中的 /system/lib/libc++.so 动态库 三.拷贝并分析 ...

  6. Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)

    在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>> ,简单的阐述 了 Android View 绘制流程的三个步骤,即: 1. ...

  7. Android中的Touch事件

    Android中的Touch事件处理 主要内容 Activity或View类的onTouchEvent()回调函数会接收到touch事件. 一个完整的手势是从ACTION_DOWN开始,到ACTION ...

  8. Android 中使用地图加载wms服务(高德地图,谷歌地图,天地图)

    转载请注明出处:http://blog.csdn.net/zkjthinking/article/details/77278838 由于公司需求需要在移动平台上加载自己发布的wms 服务: 高德地图加 ...

  9. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

最新文章

  1. 移位运算[c][code]
  2. Servlet中使用getInputStream进行文件上传
  3. 想学好Java开发,你要做到这三点
  4. OpenCV Viz转变
  5. leetcode 334. Increasing Triplet Subsequence | 334. 递增的三元子序列(一种较trick的解法)
  6. scala 加载与保存xml文档
  7. VS.net中的远程调试
  8. 3-07. 求前缀表达式的值(25) (ZJU_PAT数学)
  9. ​【文末有福利】《信条》中的物理学-时间机器存在吗?
  10. 【note】Java程序设计基础第五版(下)
  11. mysql5.5java安装_配置非安装版的mysql 5.5
  12. 微软Azure云计算服务主导全球
  13. mysql的varchar与text对比
  14. pytorch实现resnet网络结构
  15. 半小时一篇文过完C语言基础知识点
  16. GBase 8a管理集群gcware的日志-vote leader、flower、candidate部分
  17. 关于2018后新款 Mac增加T2安全芯片造成无法U盘启动解决办法
  18. shell检测硬件状态脚本
  19. 2015美国大学计算机科学专业排名,2015年US News美国大学计算机专业排名
  20. Linux非交互式修改用户密码

热门文章

  1. 驻云科技:上海云栖大会原力觉醒
  2. CTFHub | PHPINFO
  3. Python一炮句搞定网页登录验证码自动输入
  4. 软件激活的两种方式原理
  5. CSS line-hight ,vertical-align,父元素高度以及行框,行内框的关系
  6. 考c语言是什么,C语言是计算机二级吗考什么?考了有什么用
  7. php 502 bad gateway,解决lnmp服务器环境访问php文件出现502 Bad Gateway错误问题
  8. 项目四:linux QT WIFI连接工具
  9. html通过jsp连接sql数据库数据,jsp连接sql Server数据库教程(示例代码)
  10. 汕头网络货运平台等保三级的技术要求有哪些呢 甜甜告诉您