近日,谷歌在纽约如期举行了主题为“谷歌制造”(Made By Google)的硬件发布会,推出了Pixel手机、二合一平板,以及音箱等一系列新品硬件。一个月以来,包含谷歌、微软、苹果、亚马逊在内的美国四大科技巨头都相继发布了旗下最新硬件。而相比其他三家,谷歌本次的硬件品类最为全面,也彰显出更多的硬件野心。
作者简介
大家周一好,新的一周,勇往直前!
本篇转自 xong的文章,分享了关于关于在Android中使用CMake你所需要了解的一切的相关内容,一起来看看!希望大家喜欢。
xong的博客地址:
https://juejin.im/user/58c382750ce4630054690774
前言
相信大家在开发的过程中,都或多或少的接触过JNI,然后每次要接触JNI的时候,倒吸一口冷气,太难啦!
只有Java代码和C++代码 还好,在新建项目的时候把那个 "Include C++ support"勾选上,然后一路next,最后finish,一个简单的带有C++代码的Android项目就算完成了,然后在看下CMakeLists.txt怎么写的,照猫画虎就可以了,但是实际开发中,并不简单,因为底层(C++)那里给过来的有可能是.a静态库,也有可能是.so动态库,然后如何集成进项目里就是:一脸懵逼,二脸茫然,三脸不知所措。由于客观事实只能去百度,然后百度到的全是用Android.mk实现的居多,然而现在都Android Studio 3.1+时代了,还mk?关于CMake的资料又很少,所以就有了本文。
在去年在公司还是实习的时候,老大丢给我一个.a静态库(当时并不知道这是一个静态库),很是好奇,很想知道.a是怎么构建出来的,a和so的区别又是什么,总之一大堆疑问,在转正后,也尝试过要构建一个.a静态库,无奈!百度到的都是用mk的咯,找到一篇CMake的,但竟然要去Linux下面编译,还要什么自定义工具链,总之,麻烦的一批。
所以就有了这一系列,本系列不阐述什么是CMake,什么是NDK,什么是JNI,只是写一下在Android中使用CMake常遇的问题,解决方法是什么。
本系列涉及到的有:
- 初次使用CMake构建native项目
- 如何将现有的cpp代码集成到项目中
- 拷贝源码
- 编译成库文件
- CMake链接a静态库以及so动态库及动态库和静态库的区别
开始
初次使用CMake构建native项目
这个就很简单啦,新建项目的时候把 include C++ support 勾选上就可以了,但,我们还是自己动手来一遍,不依靠IDE,看看可不可以,新建普通项目,名字为:AndCmake,新建完成后如下:
新项目,什么都没有,我们都知道用CMake的话,必须要有CMakeLists.txt(注意名字不能写错),第二个就是需要的cpp资源啦,这就很简单了,在src/main目录下新建就好了,为了整洁在main目录下新建cpp文件夹,且在里面新建CMakeLists.txt和native_hello.cpp文件,然后在去CMakeLists.txt简单的配置下就好了,完成后,如下:
../src/main/cpp/native_hello.cpp
//
// Created by xong on 2018/9/28.
//
#include<jni.h>
#include <string>
extern "C"JNIEXPORT
jstring JNICALL
Java_com_xong_andcmake_jni_NativeFun_stringFromJNI(JNIEnv *env, jclass thiz)
{
std::string hello = "Hello,I from C++";
returnenv->NewStringUTF(hello.c_str());
}
这里需要说明以下,Java_com_xong_andcmake_jni_是去java的一层一层的包下面去寻找,比如这个就是去com.xong.andcmake.jni下面找,后面的NativeFun_stringFromJNI就是类和方法名了。函数的形参两个必须是这样的,第二个形参可以写成"jobject",我最近写JNI的时候,提示改成"jclass"但是,"jobject"也不能算错,也是对的。
./src/main/cpp/CMakeLists.txt
# CMake最低版本
cmake_minimum_required(VERSION 3.4.1)
# 将需要打包的资源添加进来
add_library(
# 库名字
native_hello
# 库类型
SHARED
# 包含的cpp
native_hello.cpp
)
# 链接到项目中
target_link_libraries(
native_hello
android
log
)
这就把C++部分写好了。修改../app/build.gradle,修改后如下:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++//www.58yuanyou.com_static'
}
}
...
}
...
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
...
}
写对应的Java层代码,在com.xong.andcmake包下新建jni,然后新建NativeFun类,代码如下:
packagecom.xong.andcmake.jni;
/**
* Create by xong on 2018/9/28
*/
publicclassNativeFun{
static{
System.loadLibrary( "native_hello");
}
publicstaticnativeString stringFromJNI();
}
调用NativeFun.stringFromJNI(),查看是否有正确的信息输出:这就很简单啦,在Activity中添加一个TextView然后显示下就好了,正确的话,应该会有如下显示:
OK,这样我们的第一个带有CPP代码的APP就算完成了,然后我们来思考一下,先来看native_hello.cpp的代码:
//
// Created by xong on 2018/9/28.
//
#include<jni.h>
#include <string>
extern "C"JNIEXPORT
jstring JNICALL
// 这里可不可以优化一下?
Java_com_xong_andcmake_jni_NativeFun_stringFromJNI(JNIEnv *env, jclass thiz)
{
std::string hello = "Hello,I from C++";
// 这里呢?
returnenv->NewStringUTF(hello.c_str());
}
对于我们来讲:
- 尽量不写重复代码
- 代码要高效
对于上面的函数名来讲,Java_com_xong_andcmake_jni_,假如我们在Java中对应的native类和方法,感觉在这个包中不合适,要换下包名,但是我这个类中有很多native方法,只要一换包,那么对应的cpp中的函数名就要挨个改,函数少还好办,假如有1000个,改起来真的是不要太爽,而且容易丢,那么有没有办法来尽量的减少工作量呢?类和包是相对固定的,那么我们能不能把包名抽取出来用一个表达式来表示呢?没错!就是宏定义啦!
对于下面返回的那一句,我为啥要这样写呢,因为用Android Studio创建一个带有cpp项目的时候,里面就是这样写的,然后就很自然的抄来了,但是我们想一下,有这样写的必要吗?可以看到的是在最后返回的时候,string对象又经过了一层转换点进去后可以发现,是将 string对象又转成了char数组(当然在C++中是char*指针还是const的,这里不需要知道const是啥,只需要知道,c_str()其实是又将string对象转成了char[]数组),然后我们这样写是不是就…多此一举,让计算机多做了一些事情呢?所以改完后的代码如下:
//
// Created by xong on 2018/9/28.
//
#include<jni.h>
# define XONGFUNC(name)Java_com_xong_andcmake_jni_##name
extern "C" JNIEXPORT
jstring JNICALL
XONGFUNC(NativeFun_stringFromJNI)(JNIEnv *env, jclass thiz)
{
returnenv->NewStringUTF( "Hello,I from C++");
}
这样的话,假如NativeFun不在jni包下了,而是在jnia包下,那么我们只需要将上面的宏定义改下就好了;因为我们没有对string做操作,那么我们就不需要它啦,直接返回就好了,这样计算机就不那么累了。make project后,发生了什么?
接触过JNI的同学应该了解,项目里面添加了cpp代码,apk会变的很大,那么原因嘞,那么我就来捋一捋,cpp的代码再make project后都发生了什么,都很清楚会生成so,那么…在默认的情况下会生成几个?
打开 ../app/build/intermediates/cmake/debug/obj 目录如下:
在默认的情况下会生成4个,v7a的是最大的,x86_64是最小的,默认情况下,这4个是都会打到apk里面的(因为不清楚到底是往那台手机上安装呀,只能将所有的资源都打到apk里面了),要想缩小apk,那么就把需要的so打入apk就好了。然后,问题来了,怎么才能生成规定的so库呢?上代码,如下:
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...
}
...
}
...
在../app/build.gradle中添加ndk标签,且在里面写上需要生成的架构名称就可以了,重新build下项目,如下图:
这样打入apk中的就只有v7a和x86两种架构的so库啦!下篇我们来讲解一下 现有的cpp代码集成到项目中有几种方式,以及如何操作。如何将现有的cpp代码集成到项目中?
这个在写JNI的时候就很常见了,比如json库,C++自己是没有提供json库的,然后我们在写JNI的时候,通常需要和上层交互数据,较简单的就是json了,那么就拿json来做讲解吧。首//www.58yuanyou.com先来找一个json库啦!首先把代码clone下来。源码集成进项目中
将include里的json文件夹拷贝到../app/src/main/cpp目录下。将src/lib_json里面的文件除去 CMakeLists.txt拷贝到../app/src/main/cpp目录下。最终如下:
修改../app/src/main/cpp/CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.4.1)
add_library(
native_hello
SHARED
json_tool.h
json_reader.cpp
json_valueiterator.inl
json_value.cpp
json_writer.cpp
version.h.in
# 下面的cpp若是没有则新建一个,本文基于上篇文章
native_hello.cpp
)
target_link_libraries(
native_hello
android
log
)
make build一下
What Are you!!!出错了,告诉在json_tool.h的第10行出错了,那行吧,点进去瞅一眼,如下:
哦,include错了,应该改为 #include “json/config.h” 那就改吧!cv过来的所有文件都得去查看一下(试想一下,若是cv过来的文件有1W个,咋办…,改完这个,将来别的地方在引用,又忘了那个曾经是改过的了,停!stop,我眼疼!)。
编写测试代码打开../app/src/main/cpp/native_hello.cpp 更改如下:
//
// Created by xong on 2018/9/28.
//
#include<jni.h>
#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;
constchar*name = env->GetStringUTFChars(jname, NULL);
constchar*age = env->GetStringUTFChars(jage, NULL);
constchar*sex = env->GetStringUTFChars(jsex, NULL);
constchar*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);
returnenv->NewStringUTF(root.toStyledString().c_str());
}
extern "C"JNIEXPO原由网RT
jstring JNICALL
XONGFUNC(NativeFun_parseJsonCode)(JNIEnv *env, jclass thiz,
jstring jjson)
{
constchar*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);
returnenv->NewStringUTF(out_str.c_str());
}
修改NativeFun类如下
packagecom.xong.andcmake.jni;
/**
* Create by xong on 2018/9/28
*/
publicclassNativeFun{
static{
System.loadLibrary( "native_hello");
}
publicstaticnativeString outputJsonCode(String name, String age, String sex, String type);
publicstaticnativeString parseJsonCode(String json_str);
}
测试
packagecom.xong.andcmake;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.TextView;
importcom.xong.andcmake.jni.NativeFun;
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(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", "code");
String parseJson = NativeFun.parseJsonCode(outPutJson);
tv_native_content.setText( "生成的Json:n"+ outPutJson + "n解析:"+ parseJson);
}
}
结果如下图
编译成库文件
OK,集成成功,但是太复杂,太麻烦了,每次要写那么一堆配置文件,少一个都不行,还要挨个的去改 include 想想都可怕,那么可不可以把这个jsoncpp打成库呢?那么我们就要考虑如下:
- 必须使用CMake,不使用编写mk的方式;
- 在任何系统上都可以,不可在编译库的时候切换到其他系统;
好吧,基于以上两点,百度搜了一波,发现…GG,没有符合的哎,用CMake就得去Linux下面,且需要自己构建工具链,要不然就是…mk…。再去Google搜一搜,发现还是这样的,难道,不存在?再去GitHub搜,发现没有相关的,无可奈何,去Google提供的sample中找一找吧,哈!还真有发现,链接:hello-libs
https://github.com/googlesamples/android-ndk/tree/master/hello-libs
编译so动态库
修改cpp目录为
修改../cpp/jsoncpp/目录中的CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
add_library(
# 库名字
jsoncpp
# 库类型
SHARED
# 库包含的资源
src/json_tool.h
src/json_reader.cpp
src/json_valueiterator.inl
src/json_value.cpp
src/json_writer.cpp
src/version.h.in)
# 导出目录 此处的设置 导出主目录在 Project/export文件夹内。
set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)
set_target_properties(
# 库名字
jsoncpp
# 设置输出.so动态库的路径
PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}")
add_custom_command(
# POST_BUILD 处 有三个值可选
# 分别是:
# PRE_BUILD:在 hello 运行其他规则前执行
# PRE_LINK:在编译源文件之后但在 链接其他二进制文件 或 运行静态库的库管理器 或 归档工具 之前执行
# POST_BUILD:最后执行
TARGET jsoncpp POST_BUILD
# 拷贝命令 将 ${CMAKE_CURRENT_SOURCE_DIR}/src/json/allocator.h 文件拷贝到 ${export_dir}/libsojsoncpp/include/json/ 文件夹内 且名字和之前的相同
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/allocator.h""${export_dir}/libsojsoncpp/include/json/allocator.h原由网"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/config.h""${export_dir}/libsojsoncpp/include/json/config.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/forwards.h""${export_dir}/libsojsoncpp/include/json/forwards.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/features.h""${export_dir}/libsojsoncpp/include/json/features.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/value.h""${export_dir}/libsojsoncpp/include/json/value.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/reader.h""${export_dir}/libsojsoncpp/include/json/reader.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/writer.h""${export_dir}/libsojsoncpp/include/json/writer.h"
COMMAND "${CMAKE_COMMAND}"-E copy "原由网${CMAKE_CURRENT_SOURCE_DIR}/src/json/assertions.h""${export_dir}/libsojsoncpp/include/json/assertions.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/autolink.h""${export_dir}/libsojsoncpp/include/json/autolink.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/json.h""${export_dir}/libsojsoncpp/include/json/json.h"
COMMAND "${CMAKE_COMMAND}"-E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/version.h""${export_dir}/libsojsoncpp/include/json/version.h"
)
修改 ../cpp/CMakeLists.txt如下
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
# 设置资源主目录 CMAKE_CURRENT_SOURCE_DIR 代表当前CMakeLists.txt 所在的目录
set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})
# 设置CMake编译后文件的存放的临时目录
set(lib_build_DIR $ENV{HOME}/tmp)
# 得到 lib_build_DIR 文件夹内文件
file(MAKE_DIRECTORY ${lib_build_DIR})
# 添加子目录
add_subdirectory(${lib_src_DIR}/jsoncpp ${lib_build_DIR}/jsoncpp)
修改 ../app/build.gradle如下
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
// 这里的名字最好和 ../cpp/jsoncpp/CMakeLists.txt 中设置的名字相同
targets 'jsoncpp'
}
...
}
}
...
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
}
说明:点击Build/Make Project(或者 Make Module 'app') 会在 项目根目录下 新建 export 文件夹 在里面会存放 库所需的头文件和so动态库。
编译后如下
so和头文件都生成了,但是我们在写../cpp/jsoncpp/CMakeLists.txt 文件时,也发现了,将所需的头文件导出到指定目录时有点儿费劲,只是名字换了下,若是用代码来写的话,一个for循环就可以了,那么在CMakeLists.txt中,可不可以实现类似的呢?哈哈,那当然是肯定的了,最终修改如下:
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
add_library(
jsoncpp
SHARED
src/json_tool.h
src/json_reader.cpp
src/json_valueiterator.inl
src/json_value.cpp
src/json_writer.cpp
src/version.h.in)
set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)
set_target_properties(
jsoncpp
PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}")
add_custom_command(
TARGET jsoncpp POST_BUILD
# 将 ${CMAKE_CURRENT_SOURCE_DIR}/src/json 文件夹下的文件 导出到 ${export_dir}/libajsoncpp/include/json/ 文件夹内
COMMAND "${CMAKE_COMMAND}"-E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/json""${export_dir}/libsojsoncpp/include/json/")
将之前生成的export文件夹删除,重新build发现是可以的。OK,动态库可以编译成功,那么讲道理,静态库也是一样的,来尝试下编译静态库库。
编译a静态库
在以上编译so动态库的前提下,修改../cpp/jsoncpp/CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
add_library(
jsoncpp
# 将 库 类型 由 SHARED 修改为 STATIC
STATIC
src/json_tool.h
src/json_reader.cpp
src/json_valueiterator.inl
src/json_value.cpp
src/json_writer.cpp
src/version.h.in)
set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)
set_target_properties(
jsoncpp
# 将 LIBRARY_OUTPUT_DIRECTORY 修改为 ARCHIVE_OUTPUT_DIRECTORY
# 方便查看 生成的a文件目录修改一下 即 将 libsojsoncpp 修改为 libajsoncpp
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${export_dir}/libajsoncpp/lib/${ANDROID_ABI}")
add_custom_command(
TARGET jsoncpp POST_BUILD
COMMAND "${CMAKE_COMMAND}"-E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/json""${export_dir}/libajsoncpp/include/json/"
)
修改完成后,Build/Make Project(或者 Make Module 'app') 会在 Project/export目录下生成:
这样编译.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(自行脑部表情包) 我们来一条一条的看。最后,Build/Make Module 'app'.
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 最后将所需的头文件,链接到项目中就可以啦!
- 调用代码
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;
constchar*name = env->GetStringUTFChars(jname, NULL);
constchar*age = env->GetStringUTFChars(jage, NULL);
constchar*sex = env->GetStringUTFChars(jsex, NULL);
constchar*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);
returnenv->NewStringUTF(root.toStyledString().c_str());
}
extern "C"JNIEXPORT
jstring JNICALL
XONGFUNC(NativeFun_parseJsonCode)(JNIEnv *env, jclass thiz,
jstring jjson)
{
constchar*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);
returnenv->NewStringUTF(out_str.c_str());
}
对应的Java层代码如下
packagecom.xong.andcmake.jni;
/**
* Create by xong on 2018/9/28
*/
publicclassNativeFun{
static{
System.loadLibrary( "native_hello");
}
publicstaticnativeString outputJsonCode(String name, String age, String sex, String type);
publicstaticnativeString parseJsonCode(String json_str);
}
调用代码
packagecom.xong.andcmake;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.TextView;
importcom.xong.andcmake.jni.NativeFun;
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(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层不需修改,调用时传的参数如下:
packagecom.xong.andcmake;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.TextView;
importcom.xong.andcmake.jni.NativeFun;
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(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下的库,地址如下所示:
https://blog.csdn.net/llzk_/article/details/55519242
抽取出主要的:
- 静态库
链接时间: 静态库的代码是在编译过程中被载入程序中;
链接方式: 目标代码用到库内什么函数,就去将该函数相关的数据整合进目标代码;
优点: 是在编译后的执行程序不在需要外部的函数库支持;
缺点: 如果所使用的静态库发生更新改变,程序必须重新编译。
- 动态库
链接时间: 动态库在编译的时候并没有被编译进目标代码,而是在运行时用到该库中函数时才去调用;
链接方式: 动态链接,用到该库内函数时就去加载该库;
优点: 动态库的改变并不影响程序,即不需要重新编译;
缺点: 因为函数库并没有整合进程序,所以程序的运行环境必须依赖该库文件。
再精简一点:
静态库是一堆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中取几个函数。
本来想将这一篇分成三篇来写的,想了又想,Android开发嘛,没必要对native很那么了解,所以就压缩压缩,压缩成一篇了。在这三篇中,我们发现,写CMakeLists.txt也不是那么很麻烦,而且很多都是重复的,都是可以用脚本来生成的,比如 add_library 中添加的资源文件,当然其他的也一样啦,那么这个 CMakeLists.txt 是不是可以写一个小脚本呢?我感觉可以。另一个,如何用Camke构建a静态库、so动态库,以及如何集成,在Google的sample中都有的,再贴一下链接:android_ndk
https://github.com/googlesamples/android-ndk
而且添加的时间也挺长的了,但是,百度到的文章还是 5年前的,真的是…不知道说啥了,还是多看些Google Github比较好。哈哈哈哈~
最后,上述三篇文章中涉及的源码均已上传到GitHub,链接:UseCmakeBuildLib
https://github.com/lilinxiong/UseCmakeBuildLib