相對於NDK來講SDK裏面有更多API能夠調用,有時候咱們在作NDK開發的時候,須要在JNI
直接Java中的方法和變量,好比callback
,系統信息等....
如何在JNI
中調用Java方法呢?就須要先了解FindClass
和GetMethodID
了。java
在JNI中能夠經過FindClass
能夠找到Java類,獲得jclass
,例如:android
jclass clz=(*env)->FindClass(env,"com/jjz/JniHandle");git
FindClass
的第二個參數須要傳入類的完整包名。github
使用GetMethodID
能夠獲取類的方法,獲得jmethodID,例如:數組
jmethodID getStringFromJava=(*env)->GetMethodID(env,class,"getStringForJava","()V");app
若是調用的是靜態方法須要使用GetStaticMethodID
獲取。經過FindeClass
能夠在JNI中找到須要調用的類,GetMethodID
能夠找到對應的方法,這樣就能夠在JNI中調用Java的方法了。
在GetMethodID中,第四個參數是()V
,這個是方法簽名。那麼方法簽名的規則又是怎麼樣呢?函數
在GetMethodID
中第四個參數()V
就是方法簽名,Java是支持重載的,因此須要標明方法的傳參和返回值,這就是方法的簽名。它是用來保證方法的惟一性。其中()
表明不傳參數,V
表明返回值爲void。
方法簽名對於Java的類型都有一一對應的值。方法簽名中用大寫的字母對應了java的基本數據類型:工具
Z -> booleanui
B -> bytedebug
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
。
在build
以後能夠在路徑../app/build/intermediates/classes/debug
下能夠找到build以後生成的.class
文件,運行命令:
javap -s com/jjz/JniHandle
就能夠獲得這個類的全部的方法簽名:
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
中調用Java方法了,下面分別介紹下靜態方法和類方法的調用。
調用類的靜態方法,首先要獲得類的引用,再調用類的靜態方法。
先定義一個類和靜態方法用來提供給JNI
調用:
public class JniHandle { public static String getStringFromStatic() { return "string from static method in java"; } }
在定義了一個native
方法com.jjz.NativeUtil.callJavaStaticMethodFromJni
,生成這個方法的JNI
代碼,在JNI
代碼中調用JniHandle類的靜態方法:
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); }
在Java中調用com.jjz.NativeUtil.callJavaStaticMethodFromJni
能夠該方法能夠在logcat中看到string from static method in java
,這樣就完成了在JNI
中調用了Java
靜態方法。
調用類方法要更加的複雜一些,調用步驟:
經過findClass找到類
經過GetMethodID獲得構造函數
經過調用構造函數獲得一個類的實例
經過GetMethodID獲得須要調用的方法
使用類的實例調用方法
先定義一個類方法:
public class JniHandle { public String getStringForJava() { return "string from method in java"; } }
再定義一個native
方法:com.jjz.NativeUtil.callJavaMethodFromJni
,生成該方法的JNI
代碼,在JMI
代碼中實現調用JniHandle
的類方法getStringForJava,代碼以下:
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, "<init>", "()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
,它的意思是釋放局部引用,Android VM
釋放局部引用有兩種方法:
本地方法執行完畢以後VM
自動釋放
經過調用DeleteLocalRef
手動釋放
既然上面說了VM
會自動釋放引用爲何還須要手動釋放呢?
其實某些局部變量會阻止它所引用的對象被GC回收,它所引用的對象沒法被GC回收,本身自己也就沒法被自動釋放,所以須要使用DeleteLocalRef
。而這裏使用了JNI Local Reference,在JNI中引用了Java對象,若是不使用DeleteLocalRef
釋放的話,引用沒法回收,就會形成內存泄露。