初探CMake开发NDK


事前准备

打开Android Studio->Setting->Android SDK,下载安装CMake、LLDB、NDK。

install

在新建项目的时候默认会配置好所有的环境,如果是旧的项目需要以下准备:
在gradle.properties文件中添加对过时的NDK的兼容使用

1
android.useDeprecatedNdk=true

在模块的build.gradle里面添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}

CMakeLists文件

CMakeLists

CMakeLists.txt是CMake的配置文件,用于表明版本、依赖、等信息,文件位置在app/CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 设置构建本地库所需的最小版本的cbuild。
cmake_minimum_required(VERSION 3.4.1)
# 创建并命名一个库,将其设置为静态
# 或者共享,并提供其源代码的相对路径。
# 您可以定义多个库,而cbuild为您构建它们。
# Gradle自动将共享库与你的APK打包。
add_library( hello-lib #设置库的名称。即SO文件的名称,生产的so文件为“libhello-lib.so”,在加载的时候“System.loadLibrary("hello-lib");”
SHARED # 将库设置为共享库。
src/main/jni/hello.cpp # 提供一个源文件的相对路径
src/main/jni/helloJni.cpp # 提供同一个SO文件中的另一个源文件的相对路径
)
#搜索指定的预构建库,并将该路径存储为一个变量。因为cbuild默认包含了搜索路径中的系统库,所以您只需要指定您想要添加的公共NDK库的名称。cbuild在完成构建之前验证这个库是否存在。
find_library(log-lib # 设置path变量的名称。
log # 指定NDK库的名称 你想让CMake来定位。
)
#指定库的库应该链接到你的目标库。您可以链接多个库,比如在这个构建脚本中定义的库、预构建的第三方库或系统库。
target_link_libraries( hello-lib #指定目标库中。与 add_library的库名称一定要相同
${log-lib} # 将目标库链接到日志库包含在NDK。
)
#如果需要生产多个SO文件的话,写法如下
add_library( natave-lib #设置库的名称。另一个so文件的名称
SHARED # 将库设置为共享库。
src/main/jni/nataveJni.cpp # 提供一个源文件的相对路径
)
target_link_libraries( natave-lib #指定目标库中。与 add_library的库名称一定要相同
${log-lib} # 将目标库链接到日志库包含在NDK。
)

Cpp文件

下面的代码是默认生成的:

1
2
3
4
5
6
7
JNIEXPORT jstring JNICALL
Java_com_glee_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

JNIEXPORT & JNICALL
JNIEXPORT和JNICALL这两个宏(被定义在jni.h)确保这个函数在本地库外可见,并且编译器会进行正确的调用转换。
函数规范
在JNI中C/C++的函数名是有规范要求的,由以下几部分串接而成

  • Java_前缀
  • 完全限定的类名,并用下划线_作为分隔符
  • 第一参数JNIEnv* env
  • 第二个参数jobjectjclass
  • 其他参数按类型映射
  • 返回参数按类型映射

操作

这里的操作都是新的项目中操作,如果是旧项目,在相对应的创建相对应的文件。

编写Native方法

下面来写几个Native方法,由于我们是使用kotlin代码,所以将native关键字替换为external

external将一个声明标记为不是在Kotlin中实现(通过JNI访问或者在JavaScript中实现)

1
2
external fun oneJniStr(): String
external fun helloJniStr(): String

编写Cpp函数

在Java文件中,CMake可以一键生成Cpp函数:

create

如果是kotlin文件,那么在native-lib.cpp文件里面添加下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
extern "C"
JNIEXPORT jstring JNICALL
Java_top_jowanxu_jnitest_MainActivity_oneJniStr(JNIEnv *env, jobject instance) {

return env->NewStringUTF("oneJniStr1");
}

extern "C"
JNIEXPORT jstring JNICALL
Java_top_jowanxu_jnitest_MainActivity_helloJniStr(JNIEnv *env, jobject instance) {

return env->NewStringUTF("helloJniStr1");
}

因为extern "C"的作用是告诉编译器以C方式编译。

调用

然后在Activit中调用对应方法:

1
2
3
4
sample_text.text = stringFromJNI()
fab.setOnClickListener {
sample_text.text = ("${oneJniStr()}------${helloJniStr()}")
}

结果:
screenshot

设置生成的平台

defaultConfig里面添加需要生成的平台:

1
2
3
4

ndk {
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
}

总结

使用CMake来写JNI还是特别方便的,比起以前的mk来编写,还是简单了好多,并且CMake支持一键生成native方法,支持在C语言中有代码提示,如果在C中写错了会有错误提示,支持在C中打断点进行debug

感谢