JNI方法简介

 

前言

用C/C++开发android一些功能时,不可避免的需要需要java与C/C++之间进行交互,比如数据的传递等,本文对JNI的一些相关知识做一些简述,包括JavaVM,数据类型与数据传递

1.JavaVM

Java语言的执行环境时Java虚拟机(JVM)是一个独立的进程,JNIEnv是当前java线程的执行环境,一个JVM中可以有多个java线程,每个java线程对应一个JNIEnv结构,JNIEnv也是一个函数表,通过函数表可以操作java的数据和方法。
总之,JNIEnv是一个中介,通过它可以实现java与C/C++之间的相互调用与数据传递。

2.方法调用

要想实现方法的相互调用,需要做一些处理:
1)so库里的C/C++方法需要标记为 extern "C"代码块里。

extern "C"{
      //相关方法
}

2)方法通过JNIEXPORT约定为此方法可以外部调用,通过JNICALL定音函数的入栈规则以及堆栈的清理规则(看代码提示,JNICALL是空,因为Linux上可以不用),此外还要标记此方法在哪个包里哪个类下面调用的,所以方法如下所示:

extern "C"{
JNIEXPORT jstring JNICALL
Java_javajni_NativeMethod_getNativeString(JNIEnv *env,jclass clazz){
std::string hello = "Hello world";
return env->NewStringUTF(hello.c_str());
}
}

上述代码表示Java.javajni包下有个NativeMethod类,此类中有个getNativeString方法是标记为Native的,即表示调用C/C++方法,此方法无入口参数,返回值是String类型。所有的方法中都会JNIEnv *env,jclass clazz两个参数,是代java方法调用时自动传入的,所以对java侧的方法来说就是无入口参数。至于返回值为什么是jstring,下面会说明。

3)Java中的方法调用时需要先加载对应的so(在CMakeList定义),在static中加载:

public class NativeMethod{
     static{
         System.loadLibrary("Hello");
     }
     
     public native static String getNativeString();
}

getNativeString方法就对应与Native的getNativeString方法。

3.数据传递

涉及到方法的调用,自然就会涉及到数据的传递。由于C/C++跟java对应的数据长度不同,比如int类型java恒为4个字节 ,而C/C++则跟平台有关。所以需要对应起来,在安卓中java的int类型对应C/C++的long类型,所以java对应的方法返回值是int,那么native方法对应的返回值是long。但是已经帮我们做了定义,使用时如果java方法对应的int类型那么native方法返回值应为jint类型。所以:

3.1 简单类型

简单类型如int,long,char,float,boolen等对应native方法前直接加”j",即为jint,jlong,jchar,jfloat,jboolen。

3.2 简单数组类型

简单数组类型是指int,float等类型的数组,对应的native层是jintArray和jfloatArray等,即添加前缀“j",同时添加后缀”Array“。

1)由java层传递到native的数组(以intArray为例),通过env->GetIntArrayElements方法获取数组指针,然后访问相关元素;也可以通过env->GetIntArrayRegion方法复制到一个数组中使用。

2)native层返回数组时(以intArray为例),通过env->NewIntArray创建数组,并通过env->SetIntArrayRegion为其复制

3.3 对象类型

String和类都是对象类型,不过String有独立的处理方式,通过env->NewStringUTF方法返回String类型,通过env->GetStringUTFChars获取java传递给native的String。

对于不同的类均通过jobject类型传递,所以处理起来比较复杂:
1)首先要通过env->FindClass或者env->getObjectClass获取jobject对应的类。
2)通过获取到的类,获取到内部变量的或者方法的ID,如env->GetFieldID; env->GetMethodID
3)通过获取到的ID获取变量的值或者调用相关的方法,如env->GetIntField; env->CallVoidMethod

但是由于java重载的存在,在获取相应id时要传入变量的id类型,方法的返回值类型以及入口参数。在调用获取ID的方法时(如env->GetMethodID),需要传入变量的签名,此签名就是用来标记类型的,如下所示:
env->GetFieldID(ssss, “name”, “Ljava/lang/String;”)表示或者ssss类中名为name的String类型变量。
env->GetMethodID(ssss, “getName”, “()Ljava/lang/String;”)表示获取无入口参数,返回String类型名为getName的方法ID。
注意String类型的签名后面是由分号的。

native返回对象类型比较类似,即先通过FindClass创建class对象,然后获取构造函数的方法ID(构造函数名称为""),然后通过env->NewObject创建对象,后续复制或者初始化可以参照上面的步骤。

3.4 对象数组

对象数组都是通过jobjectArray来相互传递,对于java的ArrayList则通过普通的对象(3.4所示)去处理。传入Native的对象数组可以通过env->GetObjectArrayElement来获取内部元素,并通过普通对象的处理方式来获取或者调用方法。
如果需要native返回对象数组,则需要env->NewObjectArray来创建数组,然后按照3.3的方法创建一个对象后,通过env->SetObjectArrayElement来添加内容。

4.内存释放

对于简单类型如jint,jfloat等不需要手动释放,但是对象类型以及对象类型处理过程中获取的各种ID都需要手动释放掉。
具体可参考此文

5.结语

本文只注重讲解,所以最后放几篇参考文献,里面有其他作者的示例代码。
文献一
文献二
文献三

posted @ 2022-05-03 19:39  81192  阅读(311)  评论(0编辑  收藏  举报