在实际应用中,除了在JNI层对部分功能进行C++的实现,同时还会有在JNI中对Java函数的调用以实现某种逻辑的联通。

在JNI中回调Java函数,实际上是通过反射机制来实现的,通过反射机制取得目标函数所在的类,以及其名称,通过NDK提供的接口在JNI层进行调用。

JNI中调用Java函数的栗子

TestFunction.java
package com.test.jni;
public class TestFunction {
public static void testFunc(){
Log.d("tag from Java", "worked!");
}
}
TestFunctionJNI.cpp
const char[] method_class_from_java = "com/teest/jni/TestFunction";
const char[] method_name_from_java = "testFunc";
jclass cls_str_id = jenv->FindClass(method_from_java);
jmethodID m_Java_TestFunc = jenv->GetStaticMethodID(cls_str_id, method_name_from_java, "()V");
jenv->CallStaticObjectMethod(cls_str_id, m_Java_TestFunc);

通过如上方式就可以实现在JNI中调用Java中函数,具体解释如下:

通过反射获取函数所在类的jclass;

通过反射获取目标函数的id;

通过NDK提供的接口实现调用;

其中jenv为JNI函数的环境参数,注意在反射获取java函数时其参数及返回值数据类型的签名。

NDK中数据类型签名规则

关于NDK中的签名规则如下:

字符签名

jni中类型

java中类型

V

void

void

Z

jboolean

boolean

I

jint

int

J

jlong

long

D

jdouble

double

F

jfloat

float

B

jbyte

byte

C

jchar

char

S

jshort

short

而对于数组而言,需要以"["开始,组合以上规则即可,具体对应关系表如下:

字符签名

jni中类型

java中类型

[Z

jbooleanArray

boolean[]

[I

jintArray

int[]

[J

jlongArray

long[]

[D

jdoubleArray

double[]

[F

jfloatArray

float[]

[B

jbyteArray

byte[]

[C

jcharArray

char[]

[S

jshortArray

short[]

以上均为基本数据类型的签名,对于另外两种情况:

Java类(包括自定义类)

对于参数或者返回值类型是Java类的情况,需要以"L"开头,并且以";"结束,并且以"/"隔开包名路径,并且此时在JNI中其对应接收的类型为jobject, 而对于基本数据类型,则使用其对应的NDK中的数据类型表示即可。比如:

"Landroid/os/FileUtils;"

Java内部类

对于参数或者返回值类型是Java类中的内部类的情况,则需要在以上基础结合"$"索引,比如:

"Landroid/os/FileUtils$FileStatus;"

讲到这里,基本上十分清楚了,但是有一个特殊情况,细心的同学应该可以发现,以上列表中我们并没有标记String类型。那是因为确实存在一个例外情况一定要当心,那就是String类。在使用其签名时,要使用:

"Ljava/lang/String;"
如果直接使用jstring,那就会找不到。这个例外情况一定要当心。
String[] 如何传递
当Java期待JNI中返回值为String[]时,比如:
//java native API
public native String[] getValues();
//JNI native code
JNIEXPORT jobjectArray JNICALL getValues(JNIEnv *jenv, jobject obj){
jclass stringClass = jenv->FindClass("java/lang/String");
char** user_ids = calling-other-apis //....
jobjectArray pidsArray = jenv->NewObjectArray((jsize) 10, stringClass, nullptr);
for (int i = 0; i < 10; i++) {
jstring userId = jenv->NewStringUTF(user_ids[i]);
jenv->SetObjectArrayElement(pidsArray, i, userId);
jenv->DeleteLocalRef(userId);
}
return pidsArray;
}

从以上的实例中看出,JNI中并没有'jstringArray'的类型与String[]匹配,可以替代使用的则是jobjectArray.

其中最值得关注的是以下两点:

1. 针对String的FindClass,其供反射的关键词为'java/lang/String'

(注意String的签名是'Ljava/lang/String;')

2. 该函数在JNI中注册时签名可以写为: '()[Ljava/lang/String;'

JNI中的 JNIEnv 和 jobject

在每个JNI对Java层开发的native函数中,第一第二个参数均是如下形式:

static void JNICALL test (JNIEnv *jenv, jobject obj)

JNIEnv

该参数代表Java环境,通过这个环境可以调用Java中的函数,这些函数可以在jni.h中查到,通过这些函数可以实现Java与JNI层的交互,通过JNIEnv调用JNI函数可以访问java虚拟机,操作java对象;

JNIEnv在在当前的线程有效,JNIEnv不能跨线程传递,相同的Java线程调用本地方法所使用的JNIEnv是相同的,一个native函数不能被不同的Java线程调用;该JNIEnv只想一个线程相关的结构,想成相关结构只想一个指针数组,指针数组中的内阁元素最终就会指向某一个JNI函数;

jobject

该参数代表调用jni函数的Java类或者对象,如果native方法是非静态的,那么这个参数就是对Java对象的引用,如果native函数是静态的,那么这个参数就是对Java类的class对象的引用.