相對於NDK來說SDK裡面有更多API可以呼叫,有時候我們在做NDK開發的時候,需要在JNI
直接Java中的方法和變數,比如callback
,系統資訊等….
如何在JNI
中呼叫Java方法呢?就需要先了解FindClass
和GetMethodID
了。
FindClass和GetMethodID
在JNI中可以通過FindClass
可以找到Java類,得到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
,這個是方法簽名。那麼方法簽名的規則又是怎麼樣呢?
方法簽名
在GetMethodID
中第四個引數()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
。
在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
釋放的話,引用無法回收,就會造成記憶體洩露。
原始碼地址:github.com/jjz/android…