本文已参加「新人创造礼」活动,一起敞开创造之路。
1 前言
上文说到,进行 NDK 开发的时分,咱们首要需求把 Java 办法声明为 native,然后编写对应的 C/C++ 代码,并编译成为动态链接库,在调用 Java 办法前加载动态链接库即可调用。那么,Java 层中的办法是如何与 native 层的函数一一对应的呢? 这里有两种办法:静态注册、动态注册。下面进行具体介绍。
2 静态注册
咱们运用 Android Studio 创立的 NDK 项目,默认运用的就是静态注册办法。选用静态注册时,Java 层的 native 办法与 native 层的办法在称号上具有一一对应的联系,具体要求如下:
native 层的办法名为:Java_<包名><类名><办法名>(__<参数>)
其间,包名运用下划线代替点号进行分割。只有当 native 办法出现需求重载的时分,native 层的办法名后才需求跟上参数(即上面括号里的内容),参数的编写方式与JNI签名相关(后面会介绍)。
下面是静态注册的过程:
1、创立一个测验类,通常咱们会把一切的 Native 办法放在一个类中。
package com.example.ndk;
public class NativeTest {
public native void init();
public native void init(int age);
public native boolean init(String name);
public native void update();
}
2、然后在当时类的目录下运用指令:
javac NativeTest.java
生成 NativeTest.class文件。
3、在 \app\src\main 目录下运用指令:
javah com.example.ndk.NativeTest
生成 com_example_ndk_NativeTest.h 文件。
#include <jni.h>
/* Header for class com_example_ndk_NativeTest */
#ifndef _Included_com_example_ndk_NativeTest
#define _Included_com_example_ndk_NativeTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndk_NativeTest
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_NativeTest_init__
(JNIEnv *, jobject);
/*
* Class: com_example_ndk_NativeTest
* Method: init
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_NativeTest_init__I
(JNIEnv *, jobject, jint);
/*
* Class: com_example_ndk_NativeTest
* Method: init
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_example_ndk_NativeTest_init__Ljava_lang_String_2
(JNIEnv *, jobject, jstring);
/*
* Class: com_example_ndk_NativeTest
* Method: update
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_NativeTest_update
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
在比如中,对于具有重载的 init 办法,其 native 办法称号后都带有参数,而没有重载的 update 办法则没带参数。
静态注册 JNI 办法的坏处十分明显,就是办法名会变得很长,并且当需求更改类名、包名或许办法时,需求按照之前办法从头生成头文件,灵活性不高。因而下面咱们介绍别的一种动态注册的办法。
3 动态注册
运用动态注册时,咱们需求准备好需求自己想要对应的 native 办法,然后结构 JNINativeMethod 数组,JNINativeMethod 是一种结构体,源码如下:
typedef struct {
// Java层native办法称号
const char* name;
// 办法签名
const char* signature;
// native层办法指针
void* fnPtr;
} JNINativeMethod;
然后重写 JNI_OnLoad 办法(该办法会在 Java 层通过 System.loadLibrary 加载完动态链接库后被调用),咱们在其间进行动态注册作业:
static JNINativeMethod methods[] = {
{"init", "()V", (void *)c_init1},
{"init", "(I)V", (void *)c_init2},
{"init", "(Ljava/lang/String;)Z", (void *)c_init3},
{"update", "()V", (void *)c_update},
};
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
jint result = -1;
// 获取JNI env变量
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
// 失败返回-1
return result;
}
// 获取native办法所在类
const char* className = "com/example/ndk/NativeTest";
jclass clazz = env->FindClass(className);
if (clazz == NULL) {
return result;
}
// 动态注册native办法
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return result;
}
// 返回成功
result = JNI_VERSION_1_6;
return result;
}
extern "C" JNIEXPORT void JNICALL
c_init1(JNIEnv *env, jobject thiz) {
// TODO: implement
}
extern "C" JNIEXPORT void JNICALL
c_init2(JNIEnv *env, jobject thiz, jint age) {
// TODO: implement
}
extern "C" JNIEXPORT jboolean JNICALL
c_init3(JNIEnv *env, jobject thiz, jstring name) {
// TODO: implement
}
extern "C" JNIEXPORT void JNICALL
c_update(JNIEnv *env, jobject thiz) {
// TODO: implement
}
动态注册的过程如下:
- 通过 vm( Java 虚拟机)参数获取 JNIEnv 变量
- 通过 FindClass 办法找到对应的 Java 类
- 通过 RegisterNatives 办法,传入 JNINativeMethod 数组,注册 native 函数
对于 JNINativeMethod 结构而言,签名是其十分重要的一项元素,它用于区别 Java 中 native 办法的各种重载方式,下面将介绍办法的签名。
4 办法签名
办法签名的组成规矩为:
(参数类型标识1参数类型标识2…参数类型标识n)返回值类型标识
类型标识对应联系如下:
类型标识 | Java数据类型 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L包名/类名; | 各种引证类型 |
V | void |
别的,当 Java 类型为数组时,在标识前会有“[”符号,例如:String[] 类型标识为 [Ljava/lang/String;(不要漏掉英文分号),如果有内部类则用 来分隔,如:Landroid/os/FileUtils 来分隔,如:Landroid/os/FileUtilsFileStatus;
可以根据上面的规矩手动书写办法签名,当然还有一种自动获取的办法。 如果是 ndk-build 构建的项目在\build\intermediates\classes\debug 目录下履行,如果是 CMake 构建的项目在\build\intermediates\javac\classes 目录下履行:
javap -s 全类名
如图所示:
5 总结
当了解动态注册后,动态注册无疑是注册函数的更好方式,唯一要注意的是注册函数时,别把类名、函数名和签名写错了,不然 loadLibraries 时找不到 native 办法会导致使用 crash。