我们在前面几篇文章中对JNK/NDK做了一个入门的介绍,其中使用了Android.mk和Application.mk本地配置的方式进行NDK开发。但是其实在Android Studio 2.2之后便加入了CMake方式来编译NDK代码。
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。谷歌从Android Studio2.2及更高版本使用NDK和CMake将C及C++代码编译到原生库中,其中通过Gradle便可方便地将SO库封装到APK中去。
如果你是首次使用CMake,还要跟前面安装NDK一样,在Android Studio中的SDK管理页面勾选CMake项进行下载,操作如下面图:
待安装完毕后,便可以创建Native C++项目了。在【File】 – 【New Project】弹窗中,勾上【Include C++ support】项,如下图:
项目创建好以后我们可以看到和普通Android项目有几下不同地方,如下图:
1. app目录下多出一个.externalNativeBuidl目录。
2. main目录下多出一个cpp目录,其中里头有一个native-lib.cpp文件,这便是放置C/C++代码地方。
3. app目录下的buile.gradle内容里多出两项。能看出,第一项便上我们在新建项目时选择的C++版本和勾上的-fexceptions和-frtti项,它们分别是异常支持(-fexceptions)和运行时类型信息支持(-frtti); 第二项便是指定CMakeLists.txt文件。
4. app目录下还多出一个CMakeLists.txt文件,其内容如下:
我们看回上面CMakeLists.txt文件内容,里面去除注释就剩4行有效代码,我们来看看它们的含义。
设置工程所需要的最低CMake版本,如上述最低版本是3.4.1。
添加一个库。如上述是:编译出一个动态库 native-lib,源文件只有 src/main/cpp/native-lib.cpp。参数说明:
[STATIC | SHARED | MODULE] 指定要创建的库的类型,STATIC对应的静态库(.a文件,编译时需要,相当于Windows中的lib文件)、SHARED对应共享动态库(.so文件,运行时需要,相当于Windows中的dll文件)、MODULE对应工程内的module。
[EXCLUDE_FROM_ALL] 若指定此属性,则对应的一些属性会在目录被创建时被设置,详细请查阅相应文档。
source1 source2 ... sourceN 指定源文件。
查找一个库文件。如上述是:查找预编译库log_lib。
将给定的库链接到一个目标上。如上述是:找到预编译库 log_lib 并link到动态库 native-lib中。
native-lib.cpp
#include
#include
extern "C" JNIEXPORT jstring JNICALL
Java_com_zyx_cmakedemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
非常简单的代码,在MainActivity在静态块中进行加载libnative-lib.so,然后定义了一个native 方法stringFromJNI。接着在native-lib.cpp中实现stringFromJNI方法,函数名称也是遵循规则:Java_包名_类名_方法名。
执行编译运行,这时就会在app\build\intermediates\cmake\debug\obj目录下生成对应的so文件。然后将程序运行到手机上便会看到Java成功调用了C++代码返回了结果。
在实际开发过程中,往往C++工程是跟Android工程分离,或者Android工程中直接引用外部提供现成的so库文件。现在我们就来模拟一下这种情况的发生。
首先将上述编译好的so文件拷贝到app\src\main\jniLibs中,如下图:
接着修改CMakeLists.txt内容,如下:
cmake_minimum_required(VERSION 3.4.1)
add_library(native-lib SHARED IMPORTED)
set_target_properties(native-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libnative-lib.so)
下面两行代码意思是:添加一个动态库,然后设置so库的路径,其中 ${CMAKE_SOURCE_DIR} 是CMakeLists.txt所在的路径, ${ANDROID_ABI} 是标识cup类型。
修改完后重新编译运行即可,显示结果和上面源码集成是一样的。
更多关于NDK和CMake的资料,可参考:
https://developer.android.com/ndk/guides/index.html
https://developer.android.com/ndk/guides/cmake.html.
https://www.zybuluo.com/khan-lau/note/254724
https://github.com/googlesamples/android-ndk
https://cmake.org/cmake-tutorial/