1 前言

我们在前面几篇文章中对JNK/NDK做了一个入门的介绍,其中使用了Android.mk和Application.mk本地配置的方式进行NDK开发。但是其实在Android Studio 2.2之后便加入了CMake方式来编译NDK代码。

2 CMake

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。谷歌从Android Studio2.2及更高版本使用NDK和CMake将C及C++代码编译到原生库中,其中通过Gradle便可方便地将SO库封装到APK中去。

3 Hello world

如果你是首次使用CMake,还要跟前面安装NDK一样,在Android Studio中的SDK管理页面勾选CMake项进行下载,操作如下面图:

android在kernel代码中添加log打印 android ndk cmake_add_library

待安装完毕后,便可以创建Native C++项目了。在【File】 – 【New Project】弹窗中,勾上【Include C++ support】项,如下图:

android在kernel代码中添加log打印 android ndk cmake_CMake_02

android在kernel代码中添加log打印 android ndk cmake_CMake_03

项目创建好以后我们可以看到和普通Android项目有几下不同地方,如下图:

android在kernel代码中添加log打印 android ndk cmake_add_library_04

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文件,其内容如下:

android在kernel代码中添加log打印 android ndk cmake_add_library_05

 

3.1 CMakeLists.txt解说

我们看回上面CMakeLists.txt文件内容,里面去除注释就剩4行有效代码,我们来看看它们的含义。

cmake_minimum_required(VERSION major[.minor[.patch[.tweak]]][FATAL_ERROR])
设置工程所需要的最低CMake版本,如上述最低版本是3.4.1。
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
添加一个库。如上述是:编译出一个动态库 native-lib,源文件只有 src/main/cpp/native-lib.cpp。参数说明:
<name>                                                       表示添加一个指定名称的库文件。
[STATIC | SHARED | MODULE]                指定要创建的库的类型,STATIC对应的静态库(.a文件,编译时需要,相当于Windows中的lib文件)、SHARED对应共享动态库(.so文件,运行时需要,相当于Windows中的dll文件)、MODULE对应工程内的module。
[EXCLUDE_FROM_ALL]                           若指定此属性,则对应的一些属性会在目录被创建时被设置,详细请查阅相应文档。
source1 source2 ... sourceN                     指定源文件。
find_library(<VAR> name1 [path1 path2 ...])
查找一个库文件。如上述是:查找预编译库log_lib。
target_link_libraries(<name> lib1 lib2 lib3)

将给定的库链接到一个目标上。如上述是:找到预编译库 log_lib 并link到动态库 native-lib中。

3.2 代码解说

native-lib.cpp

#include <jni.h>
#include <string>

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_包名_类名_方法名。

3.3运行

执行编译运行,这时就会在app\build\intermediates\cmake\debug\obj目录下生成对应的so文件。然后将程序运行到手机上便会看到Java成功调用了C++代码返回了结果。

android在kernel代码中添加log打印 android ndk cmake_add_library_06

 

android在kernel代码中添加log打印 android ndk cmake_CMake_07

4 引用外部so库

在实际开发过程中,往往C++工程是跟Android工程分离,或者Android工程中直接引用外部提供现成的so库文件。现在我们就来模拟一下这种情况的发生。

首先将上述编译好的so文件拷贝到app\src\main\jniLibs中,如下图:

android在kernel代码中添加log打印 android ndk cmake_CMake_08

接着修改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类型。

修改完后重新编译运行即可,显示结果和上面源码集成是一样的。