相對於NDK來說SDK裡面有更多API可以呼叫,有的時候我們在做NDK開發的時候,需要在JNI直接呼叫Java中的函式,比如callback,系統資訊等….
瞭解如何在JNI中呼叫Java方法,需要先了解FindClass
和GetMethodID
。
FindClass和GetMethodID
在JNI中可以通過FindClass
可以找到類,得到jclass,比如:
jclass clz=(*env)->FindClass(env,”com/jjz/JniHandle”);
FindClass
的第二個引數需要傳入類的路徑。
使用GetMethodID獲取類的方法,得到jmethodID,比如:
jmethodID getStringFromJava=(*env)->GetMethodID(env,class,”getStringForJava”,”()V”);
如果是靜態函式需要使用GetStaticMethodID
獲取靜態函式。
通過FindeClass可以找到JNI需要呼叫的類,GetMethodID可以找到對應的方法,這樣就可以在JNI中呼叫Java的方法了。
在GetMethodID中,第四個引數是()V
,這個是方法簽名。
方法簽名
在Get*MethodID
的使用中,其中的第四個引數()V
就是方法簽名,因為java是支援過載的,所以需要標明函式的傳參和返回值,就是方法的簽名,用來保證方法的唯一。其中()
代表不傳引數,V
代表返回值為void。
方法簽名對於Java的引數都有一一對應的值。
方法簽名中用大寫的字母對應了java的基本資料型別:
- Z -> boolean
- B -> byte
- C -> char
- S -> short
- I -> int
- J -> long
- F -> float
- D -> double
其實就是有兩個比較特殊的:boolean對應的是Z,long對應的J,其他的可以就是首個字母的大寫。
陣列的表示方法,以[
為標誌,一個[
標識一維陣列,[[表示二維陣列
- byte[] -> [B
- int[][] -> [[I
引用型別的表示方法,需要以L
開頭,以;
結束,中間對應型別的路徑比如:
- String -> Ljava/lang/String;
- Object -> Ljava/lang/Object;
自定義的類的表示方法,比如包名為jjz.example,類名為JniHandle:
- jjz.example.JniHandle ->Ljjz/example/JniHandle;
除了手動輸入類名和方法簽名以外,JDK還提供了直接生成方法簽名的工具javap
。
到../app/build/intermediates/classes/debug下可以找到build之後生成的.class檔案,執行命令:
javap -s com/jjz/JniHandle
就可以得到類的所有的方法簽名:
1 2 3 4 5 6 7 8 9 10 11 |
Compiled from "JniHandle.java" public class com.jjz.JniHandle { public com.jjz.JniHandle(); descriptor: ()V public static java.lang.String getStringFromStatic(); descriptor: ()Ljava/lang/String; public java.lang.String getStringForJava(); descriptor: ()Ljava/lang/String; } |
靜態函式的呼叫
呼叫類的靜態方法,首先要得到類的引用,再呼叫類的靜態方法。
首先定義一個類和他的靜態方法提供給JNI呼叫:
1 2 3 4 5 6 |
public class JniHandle { public static String getStringFromStatic() { return "string from static method in java"; } } |
在JNI中呼叫Java的靜態方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
JNIEXPORT void JNICALL Java_com_jjz_NativeUtil_callJavaStaticMethodFromJni(JNIEnv *env, jclass type) { jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle"); if (NULL == jniHandle) { LOGW("can't find JniHandle"); return; } jmethodID getStringFromStatic = (*env)->GetStaticMethodID(env, jniHandle, "getStringFromStatic", "()Ljava/lang/String;"); if (NULL == getStringFromStatic) { (*env)->DeleteLocalRef(env, jniHandle); LOGW("can't find method getStringFromStatic from JniHandle "); return; } jstring result = (*env)->CallStaticObjectMethod(env, jniHandle, getStringFromStatic); const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL); (*env)->DeleteLocalRef(env, jniHandle); (*env)->DeleteLocalRef(env, result); LOGW(resultChar); } |
這裡需要定義了一個native
方法com.jjz.NativeUtil.callJavaStaticMethodFromJni
,呼叫該方法可以在logcat中看到結果。
非靜態函式的呼叫
呼叫非靜態函式要複雜一些:
- 通過findClass找到類
- 通過GetMethodID得到建構函式
- 通過呼叫建構函式得到一個類的例項
- 通過GetMethodID得到需要呼叫的方法
- 使用類的例項呼叫方法
首先定義類的方法:
1 2 3 4 5 |
public class JniHandle { public String getStringForJava() { return "string from method in java"; } } |
再定義一個native
方法,com.jjz.NativeUtil.callJavaMethodFromJni
,在jni中實現該方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
JNIEXPORT void JNICALL Java_com_jjz_NativeUtil_callJavaMethodFromJni(JNIEnv *env, jclass type) { jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle"); if (NULL == jniHandle) { LOGW("can't find jniHandle"); return; } jmethodID constructor = (*env)->GetMethodID(env, jniHandle, "", "()V"); if (NULL == constructor) { LOGW("can't constructor JniHandle"); return; } jobject jniHandleObject = (*env)->NewObject(env, jniHandle, constructor); if (NULL == jniHandleObject) { LOGW("can't new JniHandle"); return; } jmethodID getStringForJava = (*env)->GetMethodID(env, jniHandle, "getStringForJava", "()Ljava/lang/String;"); if (NULL == getStringForJava) { LOGW("can't find method of getStringForJava"); (*env)->DeleteLocalRef(env, jniHandle); (*env)->DeleteLocalRef(env, jniHandleObject); return; } jstring result = (*env)->CallObjectMethod(env, jniHandleObject, getStringForJava); const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL); (*env)->DeleteLocalRef(env, jniHandle); (*env)->DeleteLocalRef(env, jniHandleObject); (*env)->DeleteLocalRef(env, result); LOGW(resultChar); } |
呼叫方法com.jjz.NativeUtil.callJavaMethodFromJni
即可看到Java中的字串傳遞給了JNI輸出到了logcat上。
程式碼中的DeleteLocalRef
的意思是釋放區域性引用,VM釋放區域性引用有兩種方法
- 本地方法執行完畢之後VM自動釋放
- 通過呼叫
DeleteLocalRef
手動釋放
既然VM會自動釋放為什麼還要手動釋放呢?
其實區域性變數會阻止它所引用的物件被GC回收,它所引用的物件無法被GC回收,自己本身也就無法被自動釋放,因此需要使用DeleteLocalRef
。
原始碼地址:https://github.com/jjz/android/tree/master/experimental