JNI知识点总结
JNI开发中静态和动态注册
JNI(Java Native Interface)是Java平台提供的一种机制,用于在Java程序中调用本地代码(例如C/C++代码)。 JNI中涉及两种类型的方法注册:静态注册和动态注册。
静态注册是指将本地方法的名称和实现直接映射到Java类的静态方法。这可以通过在C/C++代码中使用特定命名模式(例如Java_com_example_MyClass_myMethod)来实现。然后,可以使用System.loadLibrary()方法在Java代码中加载本地库,并直接调用本地方法。
动态注册是指在Java代码中使用JNI提供的一组API动态地注册本地方法。使用动态注册,可以在运行时加载本地库,并将方法名称和实现与Java类的实例方法绑定。这样,在Java代码中创建类实例时,可以直接调用本地方法。
静态注册通常比动态注册更快,因为它可以避免在Java代码中使用JNI API注册本地方法。但是,它也有一些缺点。例如,由于本地方法的名称必须遵循特定的命名约定,因此它可能不太灵活。此外,如果在运行时加载本地库时发生错误,将无法捕获它。
动态注册通常更灵活,因为它可以在运行时动态加载本地库并动态注册方法。此外,它也可以避免命名约定的限制。但是,它也比静态注册慢,因为它涉及在Java代码中使用JNI API注册本地方法。
假设有以下Java类:
public class MyNativeClass {
static {
System.loadLibrary("MyNativeLibrary");
}
public static native void myStaticMethod();
public native void myInstanceMethod();
}
此Java类具有两种本地方法:静态方法myStaticMethod和实例方法myInstanceMethod。这两个方法的实现将在本地库"MyNativeLibrary"中定义。
下面是静态注册和动态注册的具体示例:
静态注册
假设我们使用以下C/C++代码实现本地方法:
JNIEXPORT void JNICALL Java_com_example_MyNativeClass_myStaticMethod(JNIEnv *env, jclass clazz) {
// 实现静态方法
}
JNIEXPORT void JNICALL Java_com_example_MyNativeClass_myInstanceMethod(JNIEnv *env, jobject obj) {
// 实现实例方法
}
在静态注册中,必须使用特定的命名约定将本地方法名称映射到Java类的静态方法。在本例中,我们使用的是"Java_com_example_MyNativeClass_myStaticMethod"和"Java_com_example_MyNativeClass_myInstanceMethod"。这些方法必须在本地库中实现。
然后,在Java代码中,我们可以直接调用静态方法myStaticMethod(),如下所示:
MyNativeClass.myStaticMethod();
动态注册
在动态注册中,我们必须在Java代码中使用JNI API动态注册本地方法。以下是动态注册的示例代码:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
jclass cls;
JNINativeMethod methods[] = {
{"myInstanceMethod", "()V", (void*) myInstanceMethod}
};
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
cls = (*env)->FindClass(env, "com/example/MyNativeClass");
if (cls == NULL) {
return JNI_ERR;
}
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL myInstanceMethod(JNIEnv *env, jobject obj) {
// 实现实例方法
}
首先,我们实现了JNI_OnLoad()方法,在其中使用JNI API动态注册本地方法。在此示例中,我们只注册了一个实例方法myInstanceMethod()。
然后,在Java代码中,我们可以创建MyNativeClass的实例并调用本地方法myInstanceMethod(),如下所示:
MyNativeClass obj = new MyNativeClass();
obj.myInstanceMethod();
总体而言,静态注册和动态注册都可以实现在Java代码中调用本地方法。静态注册更简单,但有一些限制。动态注册更灵活,但需要在Java代码中使用JNI API注册本地方法。
jni的动态注册能解决什么问题
动态注册是JNI的一个功能,它允许我们在C/C++代码中动态地将本地方法与Java类绑定在一起。相对于静态注册,动态注册具有以下优势:
-
灵活性更高:使用动态注册,我们可以在程序运行时动态地添加或删除本地方法,而不需要重新编译整个程序。
-
可移植性更好:使用静态注册时,我们需要手动编写Java本地接口(JNI)代码,并将其与C/C++代码一起编译成共享库。这会导致库在不同的操作系统和编译器上表现不一致。而使用动态注册,我们只需要将C/C++代码编译成共享库,然后在Java代码中加载该库即可。
-
减少代码量:使用静态注册时,我们需要为每个本地方法编写一个Java本地接口(JNI)代码。而使用动态注册,我们可以使用同一个JNI接口方法来处理多个本地方法,从而减少代码量。
jni中方法签名有哪些。怎么选用
在JNI中,方法签名是指用来标识一个方法的唯一字符串,它包括了方法的名称、返回类型和参数列表。方法签名的格式为:(参数类型1, 参数类型2, ... )返回类型
,其中参数类型和返回类型都使用Java的类型描述符来表示。
下面是一些常见的Java类型描述符:
- Z:boolean类型
- B:byte类型
- C:char类型
- S:short类型
- I:int类型
- J:long类型
- F:float类型
- D:double类型
- L类名;:引用类型
- [:数组类型
例如,java.lang.String
的类型描述符为Ljava/lang/String;
,int[]
的类型描述符为[I
。
在JNI中,有几种常见的方法签名格式,如下:
(JNIEnv *, jclass)
:静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jclass对象,表示Java类对象。(JNIEnv *, jobject)
:非静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jobject对象,表示Java对象。(JNIEnv *, jobject, ...)返回类型
:非静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jobject对象,表示Java对象,后面跟着一系列方法参数,最后是返回类型。(JNIEnv *, jclass, ...)返回类型
:静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jclass对象,表示Java类对象,后面跟着一系列方法参数,最后是返回类型。
在选择方法签名时,需要注意以下几点:
- 参数类型和返回类型需要使用Java类型描述符来表示,不同类型之间使用逗号隔开。
- 方法名需要与Java中的方法名保持一致,大小写敏感。
- 对于非静态方法,需要在参数列表中添加一个jobject对象,表示Java对象。对于静态方法,需要在参数列表中添加一个jclass对象,表示Java类对象。
- 参数类型和返回类型应该与Java代码中的类型保持一致。
通常情况下,可以通过在Java代码中使用javap命令来获取方法的签名,然后在JNI代码中使用对应的签名来实现方法的调用。
使用javap命令
在Java中,可以使用javap
命令来查看一个类的方法签名。具体步骤如下:
-
在命令行中打开终端或命令提示符。
-
切换到包含要查看方法签名的类的目录。
-
输入以下命令:
javap -s <class name>
其中,
<class name>
是要查看方法签名的类的名称,不包括.class
后缀名。例如,如果要查看
MyClass
类的方法签名,可以输入以下命令:javap -s MyClass
-
然后会输出
MyClass
类的所有方法及其签名。例如:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V public void hello(); descriptor: ()V
其中,
descriptor
后面的字符串就是方法的签名。
注意,在使用javap
命令时,需要确保编译器已经编译了该类,并且该类的.class
文件已经存在。如果该类还没有被编译,可以先使用javac
命令进行编译。
另外,如果要查看一个类的所有继承方法和接口方法的签名,可以在javap
命令中加上-s
选项。
JNI和java通讯的原理是什么
JNI(Java Native Interface)是Java提供的一种与本地代码交互的机制。它提供了一组标准的接口和规范,使得Java应用程序可以调用本地库中的函数,并且本地库中的函数也可以调用Java应用程序中的函数。JNI的原理可以简单地概括为以下三个步骤:
-
Java应用程序通过JNI调用本地库中的函数。在调用之前,Java应用程序需要使用
System.loadLibrary()
或System.load()
方法加载本地库。然后,Java应用程序通过JNI提供的接口将参数传递给本地库中的函数,并且将本地库中函数的返回值传递回Java应用程序。 -
本地库中的函数接收到Java应用程序传递的参数后,通过JNI提供的接口将参数转换为本地代码可以处理的格式,并且调用本地代码中的函数进行处理。
-
本地代码中的函数处理完毕后,将结果返回给本地库中的函数。本地库中的函数将结果转换为Java应用程序可以处理的格式,并且将结果返回给Java应用程序。
总体来说,JNI的原理就是通过提供一组标准的接口和规范,使得Java应用程序可以调用本地库中的函数,并且本地库中的函数也可以调用Java应用程序中的函数,从而实现Java和本地代码之间的交互。
jni中本地引用和全局引用
JNI(Java Native Interface)中有两种引用类型:本地引用(local reference)和全局引用(global reference)。本地引用和全局引用的主要区别在于它们的生命周期和作用域范围。
本地引用是指在本地方法中创建的引用。它们只在本地方法的执行期间有效,并且不能被跨越多个本地方法调用使用。当本地方法返回时,所有本地引用都会自动失效,并且对应的内存资源也会被回收。如果在本地方法中创建的本地引用不及时释放,就会导致内存泄漏。
全局引用是指可以在Java虚拟机的整个生命周期中使用的引用。它们是通过调用JNI提供的函数NewGlobalRef()
来创建的。全局引用可以被多个本地方法使用,并且在整个Java虚拟机生命周期内都有效。当不再需要全局引用时,可以通过调用JNI提供的函数DeleteGlobalRef()
来释放对应的内存资源。
一般来说,当需要在多个本地方法中使用同一个对象时,可以将其转换为全局引用。同时,在创建本地引用时,应该尽可能地减少其数量,以避免内存泄漏的风险。在使用完本地引用后,应该及时将其释放掉,以释放对应的内存资源。
举个实例
假设有一个Java类MyClass
,其中有一个本地方法nativeMethod()
。在该方法中,需要使用一个本地引用来引用一个Java对象。同时,由于该对象需要在多个本地方法中使用,因此需要将其转换为全局引用。
java
public class MyClass {
private Object obj;
public native void nativeMethod();
public void setObj(Object obj) {
this.obj = obj;
}
public Object getObj() {
return obj;
}
}
在实现nativeMethod()
方法的本地代码中,需要使用一个本地引用来引用MyClass
对象的obj
属性,并且将其转换为全局引用。可以使用以下代码来实现:
c
JNIEXPORT void JNICALL Java_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
// 获取MyClass对象的obj属性
jfieldID fid = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, obj), "obj", "Ljava/lang/Object;");
jobject localRef = (*env)->GetObjectField(env, obj, fid);
// 将本地引用转换为全局引用
jobject globalRef = (*env)->NewGlobalRef(env, localRef);
// 在这里使用全局引用
// 释放全局引用
(*env)->DeleteGlobalRef(env, globalRef);
}
在以上代码中,GetObjectClass()
函数用于获取MyClass
对象的类引用,GetFieldID()
函数用于获取obj
属性的ID,GetObjectField()
函数用于获取obj
属性的值,并将其存储在本地引用localRef
中。接下来,可以使用NewGlobalRef()
函数将本地引用转换为全局引用,并在代码中使用它。最后,通过DeleteGlobalRef()
函数释放全局引用的内存资源。
JNI和JNA的区别是什么
JNI(Java Native Interface)和JNA(Java Native Access)都是Java程序与本地代码进行交互的技术。
JNI是Java平台提供的一种机制,它可以使Java程序调用C/C++等本地语言编写的代码,并且可以将Java对象传递到本地代码中。JNI需要开发者手动编写一些本地代码,并且需要进行编译和链接。
JNA是一个开源项目,它提供了一种更为简单的方式,使Java程序可以直接访问本地代码。JNA通过Java反射机制实现本地函数的调用,开发者不需要编写任何本地代码,只需要定义本地函数的接口并提供本地库的名称即可。JNA还支持结构体、指针等复杂类型的传递,并且可以自动进行内存管理。
因此,JNI相对而言更加灵活,可以进行更细粒度的控制,但需要更多的开发工作;而JNA则更为简单,开发效率更高,但可能会牺牲一些灵活性和性能。选择何种技术应该根据具体的需求和场景来决定。
举一个JNA实现实例
以下是一个使用JNA实现本地函数调用的简单示例:
假设有一个本地库libmymath.so,其中定义了一个函数add,用于计算两个整数的和。
创建Java接口,定义本地函数的接口。
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface MyMath extends Library {
MyMath INSTANCE = (MyMath) Native.load("mymath", MyMath.class);
int add(int a, int b);
}
加载本地库,获取接口实例。
MyMath myMath = MyMath.INSTANCE;
调用本地函数。
int result = myMath.add(1, 2);
在这个示例中,我们使用JNA加载了本地库mymath,并定义了一个接口MyMath,其中add函数的参数和返回值类型与本地库中的函数保持一致。在Java代码中,我们可以通过MyMath.INSTANCE获取接口实例,并直接调用add函数来进行本地函数调用。
JNA不是Android官方的
JNA不是Android官方实现的技术,它是一个独立的开源项目。在Android开发中,通常使用的是Android NDK(Native Development Kit)提供的JNI技术来实现Java和本地代码的交互。NDK是Android官方提供的工具集,可以帮助开发者将本地代码集成到Android应用中。
虽然JNA在Android开发中并不常用,但是在其他平台(如Windows、Linux等)上,JNA广泛应用于Java和本地代码的交互。JNA提供了一种更为简单和便捷的方式来进行本地函数调用,相对于JNI更加容易上手和使用,因此得到了不少开发者的青睐。
如何快速生成native方法
可以通过Java SE提供的javah
工具快速生成native方法。
javah
命令可以根据Java类的字节码文件,生成对应的C/C++头文件,以便我们在本地代码中实现native方法。具体步骤如下:
- 编写Java类,在其中定义native方法。
public class MyClass {
public native void nativeMethod();
}
- 在命令行中切换到Java类所在目录下,使用
javac
命令编译Java类。
javac MyClass.java
- 使用
javah
命令生成C/C++头文件。
javah MyClass
执行上述命令后,会在当前目录下生成一个名为MyClass.h
的头文件,其中包含了Java类中定义的native方法的函数原型。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MyClass */
#ifndef _Included_MyClass
#define _Included_MyClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MyClass
* Method: nativeMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_MyClass_nativeMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
- 在本地代码中实现native方法。
在生成的头文件中,可以找到对应native方法的函数原型。我们可以根据函数原型在本地代码中实现native方法,并使用System.loadLibrary
加载对应的本地库。
例如,在C/C++代码中实现nativeMethod
函数的示例代码如下:
#include "MyClass.h"
JNIEXPORT void JNICALL Java_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
// 实现native方法的代码
}
通过以上步骤,我们就可以在Java类中使用定义的native方法,并在本地代码中实现native方法。