文章目录
前言
JNI 是Java Native Interface的缩写,表示Java本地调用,通过JNI技术可以实现Java调用C程序和C程序调用Java代码。
一、在Java中调用C库函数案例
1.静态注册:
//Java 层代码JniSdk.java
public class NativeDemo {
static {
System.loadLibrary("jnisdk");
}
public native String showJniMessage();
}
//Native层代码 jnidemo.c
JNIEXPORT jstring JNICALL Java_com_dong_example_NativeDemo_showJniMessage // Java+包名+类名+方法名
(JNIEnv* env, jobject job) {
return (*env)->NewStringUTF(env, "hello world");
}
JNIEXPORT :在Jni编程中所有本地语言实现Jni接口的方法前面都有一个"JNIEXPORT",这个可以看做是Jni的一个标志,至今为止没发现它有什么特殊的用处。
jstring :这个学过编程的人都知道,当然是方法的返回值了,对应java的String类型,无返回值就是void
JNICALL :这个可以理解为Jni 和Call两个部分,和起来的意思就是 Jni调用XXX(后面的XXX就是JAVA的方法名)。
Java_NativeDemo_sayHello:这个就是被上一步中被调用的部分,也就是Java中的native 方法名:包名+类名+方法名。
JNIEnv * env:这个env可以看做是Jni接口本身的一个对象,jni.h头文件中存在着大量被封装好的函数,这些函数也是Jni编程中经常被使用到的,要想调用这些函数就需要使用JNIEnv这个对象,例如:(*env)->GetObjectClass()。
jobject obj:代表着native方法的调用者,本例即new NativeDemo();但如果native是静态的,那就是NativeDemo类。
-
静态注册的方式我们平时用的比较多。我们通过javac和javah编译出头文件,然后再实现对应的c文件的方式就是属于静态注册的方式。这种调用的方式是由于JVM按照默认的映射规则来匹配对应的native函数,如果没匹配,则会报错。
-
优点: 简单明了
-
缺点:
-
1、书写很不⽅便,因为JNI层函数的名字必须遵循特定的格式,且名字特别⻓
-
2、程序运⾏效率低,因为初次调⽤native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建⽴对应关系,这个过程⽐较耗时。
-
3、运⾏效率低,这个才是为啥不推荐使⽤静态注册的原因,因为使⽤JNI的绝⼤部分情况是为了提供效率。
2.动态注册:
//Java 层代码JniSdk.java
public class JniSdk {
static {
System.loadLibrary("jnisdk");
}
public static native int numAdd(int a, int b);
public native void dumpMessage();
}
//Native层代码 jnidemo.c
JNINativeMethod g_methods[] = {
{"numAdd", "(II)I", (void*)add},
{"dumpMessage","()V",(void*)dump},
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
j_vm = vm;
JNIEnv *env = NULL;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
jclass clazz = (*env)->FindClass(env, "com/dong/example/JnidemoClass");
//第一个参数clazz:是对应的类名的完整路径,(把.换成/)
//第一个参数g_methods:是定义的全局变量
//第一个参数2:是g_methods的数组长度,也可以用sizeof(g_methods)/sizeof(g_methods[0])
jint ret = (*env)->RegisterNatives(env, clazz, g_methods, 2);
if (ret != 0) {
LOGI("register native methods failed");
}
return JNI_VERSION_1_4;
}
- 上面的JNI_OnLoad函数是在我们通过System.loadlibrary函数的时候,JVM会回调的一个函数,我们就是在这里做的动态注册的事情,通过(*env)->RegisterNatives注册。
二、在C代码中运行Java类案例
// java层代码
public class JniSdk {
private int mIntArg = 5;
public int getArg() {
return mIntArg;
}
}
// native 层代码,c代码
void dump(JNIEnv *env, jobject obj) {
LOGI("this is dump message call: %p", obj);
jclass jc = (*env)->GetObjectClass(env, obj);
jmethodID jmethodID1 = (*env)->GetMethodID(env, jc,"getArg","()I"); // 对应java类中的方法
jfieldID jfieldID1 = (*env)->GetFieldID(env, jc,"mIntArg","I"); // 对应java类中的属性
jint arg1 = (*env)->GetIntField(env, obj,jfieldID1);
jint arg = (*env)->CallIntMethod(env, obj, jmethodID1);
LOGI("show int filed: %d, %d",arg, arg1);
}
/*
1、c代码中创建JNIEnv *env
2、一个java线程拥有一个JNIEnv,c代码调用java方法时,通常情况和java调用c代码时不在同一个
线程,所以在c线程中创建新的JNIEnv *env
*/
JNIEnv *g_env;
void *func1(void* arg) {
//进入另一个新线程
//使用全局保存的g_env,进行操作java对象的时候程序会崩溃
// jmethodID jmethodID1 = g_env->GetMethodID(jc,"getArg","()I");
// jint arg = g_env->CallIntMethod(obj, jmethodID1);
//通过这种方法获取的env,然后再进行获取方法进行操作不会崩溃
JNIEnv *env;
(*j_vm)->AttachCurrentThread(jvm,&env,NULL);
}
// java调用c时启用
void dumpArg(JNIEnv *env, jobject call_obj, jobject arg_obj) {
//打印线程
LOGI("on dump arg function, env :%p", env);
g_env = env;
pthread_t *thread;
pthread_create(thread,NULL, func1, NULL);
(*j_vm)->DetachCurrentThread(j_vm);
}
三、c库和c++库使用JNI区别
c库和c++库还是有些区别的,下面要注意的几点,不然编译不通过
- 1、头部引用
C语言 #include "jni.h"
C++ #include <jni.h>
- 2、c++需要添加extern “C”
// c++代码需要按照c的规则编译,添加extern "C"
extern "C" JNIEXPORT jstring JNICALL Java_com_dong_example_NativeDemo_showJniMessage
- 3、JNIEnv指针引用和FindClass函数参数有区别
//C语言,FindClass,反射,通过类的名字反射
jclass mainActivityCls = (*env)->FindClass(env, "com/bob/nativelib/JNITools");
//C++语言,FindClass,反射,通过类的名字反射
jclass mainActivityCls = env->FindClass("com/bob/nativelib/JNITools2");
- 4、jobject和jclass区别
// jobject:实例引⽤(C++的说法:对象引⽤),java中native前面没有static
//jclass: 类引⽤ (static native), java中native前面有static
//Java 层代码JniSdk.java
public static class NativeDemo {
static {
System.loadLibrary("jnisdk");
}
public native String showJniMessage();
}
//Native层代码 jnidemo.c
JNIEXPORT jstring JNICALL Java_com_dong_example_NativeDemo_showJniMessage
(JNIEnv* env, jclass jcl) {
return (*env)->NewStringUTF(env, "hello world");
}
四、JavaVM 和 Env 的关系
- JavaVm是虚拟机在jni层的代表,⼀个进程只有⼀个JavaVm,所有线程共⽤⼀个JavaVM。
- JNIEnv 是⼀个线程相关的结构体,它代表了java的运⾏环境 。每⼀个线程都会有⼀个,不同的线程中 不能相互调⽤,每个JNIEnv都是线程专有的。 jni中可以拥有很多个JNIEnv,可以使⽤它来进⾏java层 和native层的调⽤。
- JNIEnv 是⼀个指针,指向⼀个线程相关的结构,线程相关结构指向了JNI函数指针数组。这个数组⾥⾯ 定义了⼤量的JNI函数指针。
- 在同⼀个线程中,多次调⽤JNI层⽅法,传⼊的JNIEnv都是相同的。
- 在java层定义的本地⽅法,可以在不同的线程中调⽤,因此是可以接受不同的JNIEnv。
五、JNIEnv操作Java端的代码,主要方法:
函数名称 | 作用 |
---|---|
NewObject | 创建Java类中的对象 |
NewString | 创建Java类中的String对象 |
NewArray | 创建类型为Type的数组对象 |
GetField | 获得类型为Type的static的字段 |
SetField | 创建Java类中的对象 |
GetStaticField | 创建Java类中的对象 |
SetStaticField | 设置类型为Type的static的字段 |
CallMethod | 调用返回值类型为Type的static方法 |
CallStaticMethod | 调用返回值类型为Type的static方法 |
六、JNI数据类型
1、基本数据类型
Java Launage Type | JNI Native Type | C/C++ LaunageType | Type Description |
---|---|---|---|
boolean | jboolean | unsigned char | unsigned 8 bits |
byte | jbyte | signed char signed | 8 bits |
char | jchar | unsigned short unsigned | 16 bits |
short | jshort | signed short signed | 16 bits |
int | jint | signed int signed | 32 bits |
long | jlong | signed long signed | 64 bits |
float | jfloat | float | 32 bits |
double | jdouble | double | 64 bits |
2、引用数据类型
Java Launage Type | JNI Native Type | Type Description |
---|---|---|
java.lang.Object | jobject | 可以表示任何Java的对象,或者没有JNI对应类型的Java对象(实例⽅法的强制参数) |
java.lang.String | jstring | Java的String字符串类型的对象 |
java.lang.Class | jclass | Java的Class类型对象(静态⽅法的强制参数) |
Object[] | jobjectArray | Java任何对象的数组 |
boolean[] | jbooleanArray | Java boolean型数组 |
byte[] | jbyteArray | Java byte型数组 |
char[] | jcharArray | Java char型数组 |
short[] | jshortArray | Java short型数组 |
int[] | jintArray | Java int型数组 |
long[] | jlongArray | Java long型数组 |
float[] | jfloatArray | Java float型数组 |
double[] | jdoubleArray | Java double型数组 |
java.lang.Throwable | jthrowable | Java的Throwable类型,表示异常的所有类型和⼦类 |
void | void | N/A |
七、域描述符、方法描述符
1、 基本类型域描述符
Java Launage Type | Field Description | 备注 |
---|---|---|
int | I | int的⾸字⺟、⼤写 |
long | L | long的⾸字⺟、⼤写 |
byte | B | byte的⾸字⺟、⼤写 |
short | S | short的⾸字⺟、⼤写 |
char | C | char的⾸字⺟、⼤写 |
double | D | double的⾸字⺟、⼤写 |
boolean | Z | 因B已被byte使⽤,所以JNI规定使⽤Z |
void | V | ⽆返回类型 |
object | L+类全名+; | String 如: Ljava/lang/String |
array | [+类型描述符 | int[] 如:[I |
Method | (参数)返回值 | int add(int a, int b) 如:(II)I |
java类中类 | 使用$ | Landroid/media/MediaCodec$CryptoInfo |
2、⽅法描述符
Java Method | Method Description |
---|---|
String fun() | ()Ljava/lang/String; |
int fun(int i, Object object) | (ILjava/lang/Object;)I |
void fun(byte[ ] bytes) | ([B)V |
int fun(byte data1, bytedata2) | (BB)I |
void fun() | ()V |