Android NDK - JNI中回調Java中的函數及函數簽名

在實際應用中,除了在JNI層對部分功能進行C++的實現,同時還會有在JNI中對Java函數的調用以實現某種邏輯的聯通。
在JNI中回調Java函數,其實是經過反射機制來實現的,經過反射機制取得目標函數所在的類,以及其名稱,經過NDK提供的接口在JNI層進行調用。java

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中函數,具體解釋以下:android

  • 經過反射獲取函數所在類的jclass;
  • 經過反射獲取目標函數的id;
  • 經過NDK提供的接口實現調用;

其中jenv爲JNI函數的環境參數,注意在反射獲取java函數時其參數及返回值數據類型的簽名。web

NDK中數據類型簽名規則

關於NDK中的簽名規則以下:api

字符簽名 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[]

以上均爲基本數據類型的簽名,對於另外兩種狀況:svg

  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.code

其中最值得關注的是如下兩點:

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對象的引用.