静态注册
默认情况下,就是静态注册,静态注册是最简单的方式,NDK开发过程中,基本上使用静态注册。
新建一个Java工程。添加运行测试Java文件
public class TestDemo {
public native String stringFromJNI(); // 静态注册
public native void staticRegister(); // 静态注册
public static void main(String[] args) {
TestDemo demo = new TestDemo();
System.out.println(demo.stringFromJNI());
}
}
使用javah
自动生成JNI C函数头文件
$ javah com.jni.dynamic.register.TestDemo
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_jni_dynamic_register_TestDemo */
#ifndef _Included_com_jni_dynamic_register_TestDemo
#define _Included_com_jni_dynamic_register_TestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jni_dynamic_register_TestDemo
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI
(JNIEnv *, jobject);
/*
* Class: com_jni_dynamic_register_TestDemo
* Method: staticRegister
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI(JNIEnv *, jobject);
和JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister (JNIEnv *, jobject);
这种通过javah默认生成C函数方法声明的方式就是静态注册
通过Clion新建C++工程,com_jni_dynamic_register_TestDemo.h
放入工程中
#include "com_jni_dynamic_register_TestDemo.h"
#include
#include
using namespace std;
JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI
(JNIEnv *env, jobject thiz) {
std::string hello = "默认就是静态注册哦";
cout << hello << endl;
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister
(JNIEnv *env, jobject thiz) {
cout << "staticRegister" << endl;
}
生成动态库
add_library(dynamicRegister SHARED dynamic_register.cpp)
个人使用的macOS 所以动态库是.dylib
"静态注册"方法确实帮我们省了很多事情,但是也有相应的缺点
首次调用 Java 的 native 方法,虚拟机会去搜寻对应的 Native 层的函数,这就有点影响执行效率了。如果搜索到了,就会建立映射关系,下次就不用再浪费时间去搜索了( 运行期 才会去 匹配JNI函数,性能上 低于 动态注册)。
Native 层的函数名字太长,名字的格式为
Java_包名_类名_方法名
,例如Java_com_jni_dynamic_register_TestDemo_staticRegister
。
有"静态注册",当然就有"动态注册",那么相比较而言,有哪些优缺点呢
- "动态注册"需要我们手动建立函数映射关系,虽然增加了代码量,但是可以提供运行效率。
- "动态注册"允许我们自定义函数名字。
- 相比于"静态注册",工作效率高。
虽然"动态注册"相比于"静态注册"有这么多好处,但是需要一定的学习成本,但是这也是非常值得的,那么我们就开始吧。
加载动态库
我们知道,在 Java
层通过 System.loadLibrary()
方法可以加载一个动态库,此时虚拟机就会调用JNI库中的 JNI_OnLoad()
函数(放在哪个文件无所谓),函数原型如下
#include
jint JNI_OnLoad(JavaVM* vm, void* reserved);
参数介绍
-
vm
:JavaVM
指针 -
reserved
: 类型为void *
,这个参数是为了保留位置,以供将来使用。
返回值代表被动态库需要的JNI
版本,当然,如果虚拟机无法识别这个返回的版本,那么动态库也加载不了。
目前已有的返回值有四个,分别为 JNI_VERSION_1_1
, JNI_VERSION_1_2
, JNI_VERSION_1_4
, JNI_VERSION_1_6
。
如果动态库没有提供 JNI_OnLoad()
函数,虚拟机会假设动态库只需要 JNI_VERSION_1_1
版本即可,然而这个版本太旧,很多新的函数都没有,因此我们最好在动态库中提供这个函数,并返回比较新的版本,例如 JNI_VERSION_1_6
。
//====================dynamic_register.cpp==================
#include "com_jni_dynamic_register_TestDemo.h"
#include
#include
using namespace std;
JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI
(JNIEnv *env, jobject thiz) {
std::string hello = "默认就是静态注册哦";
cout << hello << endl;
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister
(JNIEnv *env, jobject thiz) {
cout << "staticRegister" << endl;
}
// JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {
cout << "JNI_OnLoad" << endl;
return JNI_VERSION_1_6; // // AS的JDK在JNI默认最高1.6 存Java的JDKJNI 1.8
}
注册函数
JNI_OnLoad()
函数经常会用来做一些初始化操作,"动态注册"就是在这里进行的。
"动态注册"可以通过调用_JNIEnv
结构体的 RegisterNatives()
函数
struct _JNIEnv {
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
#endif /*__cplusplus*/
}
实际使用的函数原型如下
jint RegisterNatives(JNIEnv *env, jclass clazz,
const JNINativeMethod *methods, jint nMethods);
参数解释
-
env
:JNIEnv
指针. -
clazz
: 代表 Java 的一个类。 -
methos
: 代表结构体JNINativeMethod
数组。JNINativeMethod
结构体定了Java层的native
方法和底层的函数的映射关系。 -
nMethods
: 代表第三个参数methods
所指向的数组的大小。
返回值
0代表成功,负值代表失败。
JNINativeMethod结构体
RegisterNatives()
函数最重要的部分就是 JNINativeMethod
这个结构体,我们看下这个结构体声明
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
name
表示Java
的native
方法的名字。
signature
表示方法的签名。
fnPtr
是一个函数指针,指向JNI
层的一个函数,也就是和Java
层的native
建立映射关系的函数。
实现动态注册
有了前面的所有基础,那么我们就可以实现自己的动态注册功能了。
首先带有native
方法的Java
类如下
public class TestDemo {
static {
System.load("/Volumes/CodeApp/SourceWork/CPlus_workspace/JNI-Demo-Lib/cmake-build-debug/dynamic_register/libdynamicRegister.dylib");
}
public native String stringFromJNI(); // 静态注册
public native void staticRegister(); // 静态注册
public native void dynamicJavaMethod01(); // 动态注册1
public native int dynamicJavaMethod02(String valueStr); // 动态注册2
public static void main(String[] args) {
}
}
根据头文件的注释和函数原型,我们就可以实现如下的动态注册
// native 真正的函数
// void dynamicMethod01(JNIEnv *env, jobject thiz) { // OK的
void dynamicMethod01() { // 也OK 如果你用不到 JNIEnv jobject ,可以不用写
cout << "我是动态注册的函数 dynamicMethod01..." << endl;
}
int dynamicMethod02(JNIEnv *env, jobject thiz, jstring valueStr) { // 也OK
const char *text = env->GetStringUTFChars(valueStr, nullptr);
cout << "我是动态注册的函数 dynamicMethod02... " << text << endl;
env->ReleaseStringUTFChars(valueStr, text);
return 200;
}
/*
typedef struct {
const char* name; // 函数名
const char* signature; // 函数的签名
void* fnPtr; // 函数指针
} JNINativeMethod;
*/
static const JNINativeMethod jniNativeMethod[] = {
{"dynamicJavaMethod01", "()V", (void *) (dynamicMethod01)},
{"dynamicJavaMethod02", "(Ljava/lang/String;)I", (int *) (dynamicMethod02)},
};
const char *mainClassName = "com/jni/dynamic/register/TestDemo";
// JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {
JNIEnv *jniEnv = nullptr;
int result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);
// result 等于0 就是成功
if (result != JNI_OK) {
return -1; // 会奔溃
}
cout << "System.loadLibrary ---》 JNI Load init"<< endl;
jclass mainClass = jniEnv->FindClass(mainClassName);
// jint RegisterNatives(Class, 我们的数组==jniNativeMethod, 注册的数量 = 2)
jniEnv->RegisterNatives(mainClass,
jniNativeMethod,
sizeof(jniNativeMethod) / sizeof(JNINativeMethod));
cout << ("动态 注册没有毛病") << endl;
return JNI_VERSION_1_6; // // AS的JDK在JNI默认最高1.6 存Java的JDKJNI 1.8
}
public static void main(String[] args) {
System.out.println("-------------------start main-------------------");
TestDemo demo = new TestDemo();
demo.dynamicJavaMethod01();
System.out.println("-------------------华丽的分割线-------------------");
demo.dynamicJavaMethod02("hello 2222");
}
RUN>
System.loadLibrary ---》 JNI Load init 动态 注册没有毛病 -------------------start main------------------- 我是动态注册的函数 dynamicMethod01... -------------------华丽的分割线------------------- 我是动态注册的函数 dynamicMethod02... hello 2222
image
总结
"动态注册"功能还是比较简单的,只需要搞清楚JNI_OnLoad()
和RegisterNatives()
函数的使用就行。
Demo-GitHub