事前准备
打开Android Studio->Setting->Android SDK,下载安装CMake、LLDB、NDK。
在新建项目的时候默认会配置好所有的环境,如果是旧的项目需要以下准备:
在gradle.properties文件中添加对过时的NDK的兼容使用1
android.useDeprecatedNdk=true
在模块的build.gradle
里面添加1
2
3
4
5
6
7
8
9
10
11
12
13
14android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
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
7JNIEXPORT 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
- 第二个参数
jobject
或jclass
- 其他参数按类型映射
- 返回参数按类型映射
操作
这里的操作都是新的项目中操作,如果是旧项目,在相对应的创建相对应的文件。
编写Native
方法
下面来写几个Native
方法,由于我们是使用kotlin代码,所以将native
关键字替换为external
。
external
将一个声明标记为不是在Kotlin中实现(通过JNI
访问或者在JavaScript
中实现)
1 | external fun oneJniStr(): String |
编写Cpp函数
在Java文件中,CMake
可以一键生成Cpp
函数:
如果是kotlin文件,那么在native-lib.cpp
文件里面添加下面代码:1
2
3
4
5
6
7
8
9
10
11
12
13extern "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
4sample_text.text = stringFromJNI()
fab.setOnClickListener {
sample_text.text = ("${oneJniStr()}------${helloJniStr()}")
}
结果:
设置生成的平台
在defaultConfig
里面添加需要生成的平台:1
2
3
4
ndk {
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
}
总结
使用CMake
来写JNI还是特别方便的,比起以前的mk
来编写,还是简单了好多,并且CMake
支持一键生成native
方法,支持在C语言中有代码提示,如果在C中写错了会有错误提示,支持在C中打断点进行debug
。