Android NDK - JNI中回调Java中的函数及函数签名

在实际应用中,除了在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[]

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

  1. Java类(包括自定义类)
    对于参数或者返回值类型是Java类的情况,需要以"L"开头,并且以";“结束,并且以”/"隔开包名路径,并且此时在JNI中其对应接收的类型为jobject, 而对于基本数据类型,则使用其对应的NDK中的数据类型表示即可。比如:
"Landroid/os/FileUtils;"
  1. 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对象的引用.

你可能感兴趣的:(Android,计算机视觉,ndk,java)