NDK,既原生开发套件,是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码。
- Android 原生开发套件 (NDK):这套工具使您能在 Android 应用中使用 C 和 C++ 代码。
- CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。强烈建议使用Cmake。
- LLDB:Android Studio 用于调试原生代码的调试程序。
这篇文章不是入门文章,Android有详细入门指引。也有其他博客介绍ndk-build和Cmake开发入门。
1、JNI说起
JNI,全称Java NativeInterface,一种本地应用程序标准的应用程序接口。它允许运行在JVM上的代码与C/C++实现交互。
2、类型映射表
Java |
JNI |
签名缩写 |
说明 |
boolean |
jboolean |
Z |
无符号8为整数 |
byte |
jbyte |
B |
有符号8位整数 |
char |
jchar |
C |
无符号16位整数 |
short |
jshort |
S |
有符号16位整数 |
int |
jint |
I |
有符号32位整数 |
long |
jlong |
J |
有符号64位整数 |
float |
jfloat |
F |
32位浮点数 |
double |
jdouble |
D |
64位浮点数 |
void |
Void |
V |
- |
String |
- |
Ljava/lang/String; |
L完整包名加类名; |
int[] |
jintArray |
[I |
数组的时候在前面加[ |
3、本地函数注册
JNI里面函数注册之后,才能被Java或Kotlin调用!对应Java的native关键字修饰的函数或者Kotlin的external关键字修饰的函数。
静态注册(Java代码)
这种方法最常见也最简单,初学者首选。可以用javah -jni
命令直接生成native函数对应的头文件。
比如,有如下Java函数:
1
2
3
4
|
package com.qfleng.jpeg;
public class JpegEngine {
public static native int[] decodeJpeg(String path, ImageInfo img_info);
}
|
对应的C++静态注册函数如下:
1
2
|
JNIEXPORT jintArray JNICALL
Java_com_qfleng_jpeg_JpegEngine_decodeJpeg(JNIEnv *env, jobject obj, jstring path, jobject img_info){ }
|
由于静态注册函数签名包含了Java包名部分,非常冗长;调用时,搜索效率没有动态注册的效率高;函数量大的时候也不利于管理代码。
动态注册(Kotlin代码)
动态注册,是在JNI_OnLoad函数里面通过RegisterNatives函数实现的。RegisterNatives函数需要传入JNINativeMethod结构体,这其中保存着本地函数和native函数的对应关系。
JNINativeMethod结构体:
1
2
3
4
5
|
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
|
比如有如下Kotlin函数:
1
2
3
4
|
package com.qfleng.opencv
object CvUtil{
private external fun nGaussianBlur(srcAddr: Long, dstAddr: Long, radius: Int)
}
|
对应动态注册的C++代码如下:
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
28
29
30
31
32
33
34
|
const char *classPathName = "com/qfleng/opencv/CvUtil";
void gaussian_blur(JNIEnv *env, jobject thiz, jlong src_addr, jlong dst_addr, jint radius) {
Mat &srcMat = *(reinterpret_cast<Mat *>(src_addr));
Mat &dstMat = *(reinterpret_cast<Mat *>(dst_addr));
GaussianBlur(srcMat, dstMat, Size(radius, radius), 0, 0);
}
static JNINativeMethod methods[] = {
{"nGaussianBlur", "(JJI)V", (void *) gaussian_blur},
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jclass clazz = NULL;
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("JNI_OnLoad->GetEnv error!");
return -1;
}
clazz = env->FindClass(classPathName);
if (!clazz) {
LOGE("JNI_OnLoad->FindClass error!");
return -1;
}
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
LOGE("JNI_OnLoad->RegisterNatives error!");
return -1;
}
return JNI_VERSION_1_6;
}
|
动态注册,虽然代码优美、效率也高,但不能命令生成,需要手动注册并管理对应关系。函数签名便是其中重点:
如上面Kotlin代码fun nGaussianBlur(srcAddr: Long, dstAddr: Long, radius: Int)
对应的注册签名如下(JJI)V
。
jstring和char转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//char*转jstring
jstring charToJString(JNIEnv *env, char *src, size_t len) {
jbyteArray array = env->NewByteArray(len);
env->SetByteArrayRegion(array, 0, len, (const jbyte *) src);
jstring strEncode = env->NewStringUTF("UTF-8");
jclass cls = env->FindClass("java/lang/String");
jmethodID ctor = env->GetMethodID(cls, "<init>", "([BLjava/lang/String;)V");
jstring object = (jstring) env->NewObject(cls, ctor, array, strEncode);
return object;
}
//将jstring转换成为UTF-8格式的char*
std::string jstringToString(JNIEnv *env, jstring srcStr) {
const char *tmpChar = env->GetStringUTFChars(srcStr, JNI_FALSE);
std::string result(tmpChar);
env->ReleaseStringUTFChars(srcStr, tmpChar);
return result;
}
|
需要特别注意的是,调用GetStringUTFChars后需要调用ReleaseStringUTFChars函数。
其他