文件是x86
的架构。那么就需要在jniLibs
目录下新建x86
的目录下,然后再把libsum.a
放到该目录下。至此,静态库的构建工作就算结束了。
使用静态库
把静态库放到合适的位置后,我们需要配置app
目录下的build.gradle
和cpp
目录下的CMakeLists.txt
文件,完成静态库的引入。
配置Gradle
首先说build.gradle
,该文件主要涉及到修改ABI的问题,因为不指定的话,Gradle默认生成的ABI可能找不到对应的静态库文件来链接,从而导致链接失败。该文件主要的修改如下
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
abiFilters "x86"
}
}
}
}
也就是把abiFilters
的值指定为刚才构建的静态库相同的值。
配置CMake
而CMakeLists.txt
文件就复杂一些了,它需要完成两个工作,找到静态库和静态库的头文件,链接静态库。
找到头文件
在文章的第二部分我们已经知道了让CMake找到头文件的include_directories
命令,把参数设置为3rd
目录就行了。值得注意的是,CMake是以当前的CMakeLists.txt
文件为工作目录的,所以,要指定到3rd
文件,我们需要一直回退目录到根项目,最终就有了include_directories(../../../../../3rd)
这样的配置。尽量使用相对路径,可以在多人协同的情况下,不用修改配置。
找到静态库
下一步要让CMake找到我们的静态库。说到库,都是和add_library
相关的,不同的只是参数。使用源码添加库的时候,我们需要指定库的名称和源码位置,而引用第三方库,则是需要指定库的名称和类型,外加一个IMPORTED
的指示参数,告诉CMake这个库是导入的。所以就有了add_library(addSum STATIC IMPORTED)
这样的配置。
但是,这里我们只告诉了CMake库的名字,库存储在哪里,还不知道,所以我们还需要另一个命令告诉CMake库的存储位置。涉及到配置参数的,通常就是set_target_properties
命令了,可以多次调用这个命令设置多种配置。set_target_properties(addSum PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libsum.a)
,第一个参数和上一条的第一个参数是一一对应的,可以随便取。其实add_library
相当于生成了一种目标产物,用第一个参数来指代这种产物,所以才让我们的set_target_properties
找得到合适的目标设置属性。第二个参数则是配置属性的标准写法,第三个代表属性变量,第四个是属性值,配置库路径的变量就是IMPORTED_LOCATION
,而值这里就有个坑了,Android下的CMake限定值必须是绝对路径,不能是相对路径。而这与使用CMake的初衷背道而驰,幸好,我们有几个预设值可以用,CMAKE_CURRENT_SOURCE_DIR
就是其中之一,它代表着当前这个CMakeLIsts.txt
文件的绝对路径,有了这个,再加上目录的回退功能,我们就能找到任何合适的目录了。至此,又出现了第二个问题,当有多个架构的静态库需要配置时,我们引入的目录是不一样的,而且会出现很多重复的配置。还好有ANDROID_ABI
的帮助,它指代了当前编译的某个架构,随着编译的进行,这个值会被设置为合适的值,并且是和正在编译的架构是一一对应的。所以,尽管它们有点奇怪,但是这给我带来了灵活和简单。
链接静态库
现在头文件有了,库也有了,但是C++的编译是分成两步的,目前为止,我们的工作只做完了编译的事情,还没涉及到链接的事情,当然,相比前面的配置,这就简单多了,无疑就是在target_link_libraries
命令里添加一个参数就可,如
target_link_libraries(
native-lib
${log-lib}
addSum
)
只需哟注意名字和add_library
时配置的名字一一对应就可。
在源码中使用
经过漫长的等待,现在我们终于能在native-lib.cpp
文件中引入addsum
的头文件,并且使用里面的函数完成工作了。我打算让函数返回一个包含加法运算结果的字符串。最终实现如下
#include <jni.h>
#include <string>
#include <lib.h>
extern "C" JNIEXPORT jstring JNICALL
Java_me_hongui_cmakesum_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = std::to_string(sum(1,1));
return env->NewStringUTF(hello.c_str());
}
至此,点击工具栏上的run
按钮,我们终于可以在Android的模拟器上看到我们的静态库工作的成果啦。
扩展
其实除了引用静态库的方式之外,我们还可以直接通过配置CMakeLists.txt
文件来引用源码,这样可以随时随地对源码进行定制,但是也降低了编译速度,而且可能会增加CMakeLists.txt
的复杂度。所以我还是推荐直接使用静态库的方式。
总结
CMake其实还有很多很多命令,我们这里涉及到的只是很少的一部分。但是,我觉得理解CMake有这些内容差不多就可以了,后续有需要再针对性学习就行了。学习一门技术,切忌不能贪多,贪细。先要抓住主干,理清脉络,后面的细节就是水到渠成的事。对于CMake,我觉得就是以C++代码编译为二进制的过程为主干就够了。源码从哪里来,头文件在那里,库文件在哪里,怎么组织编译,参与链接的库有哪些,生成什么产物,还有一些完成这些工作的通用操作,复制文件啊,目录信息啊等,这些操作的集合就构成了CMake的主体。另外,CMake其实只是一种构建工具,它本身不是编译器和链接器,有些问题可能不仅仅会涉及到cmake,还可能会涉及到编译器和连接器。当然,这些都是后面深入了解之后才可能碰到的问题了。