我們在上一篇文章《Android中JNI&NDK入門(一) 之 初識NDK和JNI》中已經通過Demo演示瞭如何在Java代碼中去調用C++代碼,今天繼續來看看在JNI中是如何反調用Java方法的。先大概提一下,JNI中要調用Java方法的流程是先通過類名找到類,然後再根據方法名找到方法的id,最後就可以調用這個方法了。如果是調用Java中的非靜態方法,那麼就需要先構造出類的對象後才能調用它。其實實現跟我們平時在Java中使用反射是非常相似的,下面通過擴展上一篇文章中的Demo來看看JNI中反調用Java方法是實現。
1 擴展Hello world
修改JNIUtils.java,使其增加一個靜態方法供JNI中去反調用:
public class JNIUtils {
static {
System.loadLibrary("jni-demo");
}
public static native String getInfo();
public static String getJavaInfo(String info, int index) {
return info + index;
}
}
修改JNIUtils.cpp:
#include "com_zyx_jnidemo_JNIUtils.h"
#include <stdio.h>
#include <android/log.h>
JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo(JNIEnv * env, jclass thiz) {
__android_log_print(ANDROID_LOG_DEBUG, "zyx", "Hello world from JNI !");
//return env->NewStringUTF("Hello world from JNI !");
jclass clazz = env->FindClass("com/zyx/jnidemo/JNIUtils");
if (clazz == NULL) {
return env->NewStringUTF("find class error");
}
jmethodID methodId = env->GetStaticMethodID(clazz, "getJavaInfo", "(Ljava/lang/String;I)Ljava/lang/String;");
if(methodId == NULL) {
return env->NewStringUTF("find method error");
}
jstring info = env->NewStringUTF("Hello world from JNI !");
jint index = 2;
return (jstring)env->CallStaticObjectMethod(clazz, methodId, info, index);
}
非常簡單的修改,然後再重新編譯程序,運行可見:
2 Native代碼反調用Java層代碼
從上面修改後的代碼中可見,Java_com_zyx_jnidemo_JNIUtils_getInfo函數中首先通過FindClass函數傳入類名:com/zyx/jnidemo/JNIUtils來找到了類,然後再使用GetStaticMethodID函數傳入類的變量、方法名 和 方法簽名來找到了方法,接着再通過JNIEnv對象的CallStaticObjectMethod函數傳入類、方法和兩個對應的參數來完成最終的調用過程。這樣一來就完成了一次從Java調用JNI然後再從JNI中調用Java方法的過程。其中,FindClass、GetStaticMethodID和CallStaticObjectMethod這些函數都是在jni.h中定義的,下面我們來看看這些函數的使用詳細:
2.1 獲取Class對象
jclass FindClass(const char* clsName)
通過類的名稱(包名,但是要用"/"替換'".")來獲取jclass。比如上面代碼:jclass clazz = env->FindClass("com/zyx/jnidemo/JNIUtils");
jclass GetObjectClass(jobject obj)
通過對象實例來獲取jclass,相當於Java中的getClass()函數
jclass getSuperClass(jclass obj)
通過jclass可以獲取其父類的jclass對象
2.2 獲取屬性方法
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
獲取某個屬性
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
獲取某個方法
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
獲取某個靜態屬性
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
獲取某個靜態方法。比如上面代碼:jmethodID methodId = env->GetStaticMethodID(clazz, "getJavaInfo", "(Ljava/lang/String;I)Ljava/lang/String;");
2.3 構造一個對象
jobject NewObject(jclass clazz, jmethodID methodID, ...)
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args)
jobject NewObjectV(jclass clazz, jmethodIDmethodID,va_list args)
構造出一個對象,比如:
jmethodID methodId = env->GetMethodID(clazz, "方法名", "方法簽名");
jobject obj = env->NewObject(clazz, methodId);
2.4 調用方法
jobject (*CallStaticXXXMethod)(JNIEnv*, jclass, jmethodID, ...);
調用某個靜態方法,並返回XXX,比如上面代碼:(jstring)env->CallStaticObjectMethod(clazz, methodId, info, index);
jobject (*CallXXXMethod)(JNIEnv*, jobject, jmethodID, ...);
調用某個對象靜態方法,並返回XXX
3 方法簽名
上面擴展Demo中提到使用GetStaticMethodID函數傳入的第三個參數是方法簽名:(Ljava/lang/String;I)Ljava/lang/String; ,那麼這個方法簽名到底是什麼?又是如何得到的呢?下面我們就來講講方法簽名是怎麼一回事。
因爲Java支持方法重載,即同樣的方法名,可以有不一樣的參數或返回類型。那麼JNI如果僅僅是根據方法名是沒有辦法找到重載的方法的,所以要解決這個問題,JNI就衍生了一個簽名的概念,也就是將參數類型和返回值類型進行組合,如果擁有一個該函數的簽名信息和這個函數的函數名,我們就可以找到對應的Java層中的函數了。
2.1 使用命令查看方法簽名
在命令行中通過命令:javap -s xxx.class便能查看到整個類裏方法的簽名,如圖:
紅框中便是上面Demo中要傳入的值了。
2.2 推算方法簽名
JNI的數據類型包含兩種:基本類型和引用類型。而類型簽名是標識一個特定的Java類型。
基本類型主要有jboolean、jchar、jint等,它們大多和Java中的數據類型相對應,只是前面多一個j,而簽名大多是首字母大寫,特殊除外,如下表:
JNI類型 |
Java類型 |
類型簽名 |
jboolean |
boolean |
Z (因爲B被byte使用) |
jbyte |
byte |
B |
jchar |
char |
C |
jshort |
short |
S |
jint |
int |
I |
jlong |
long |
J (因爲L表示類的簽名) |
jfloat |
float |
F |
jdouble |
double |
D |
void |
void |
V |
引用類型主要有類、對象和數組,類的簽名比較簡單,它採用”L+包名+類名+;”(特別要注意分號不要漏)的形式,只需要將其中的 . 替換爲 / 即可,而數據類型數組就是“[ +數據類型簽名”,如果是多維數組,它的簽名爲n個[ + 類型簽名,對應關係如下表:
JNI類型 |
Java類型 |
類型簽名 |
jobject |
Object |
Ljava/lang/Object; |
jclass |
Class |
Ljava/lang/Class; |
jstring |
String |
Ljava/lang/String; |
jobjectArray |
Object[] |
[Ljava/lang/Object; |
jbooleanArray |
boolean[] |
[Z |
jbyteArray |
byte[] |
[B |
jcharArray |
char[] |
[C |
jshortArray |
short[] |
[S |
jintArray |
int[] |
[I |
jlongArray |
long[] |
[J |
jfloatArray |
float[] |
[F |
jdoubleArray |
double[] |
[D |
jthrowable |
Throwable |
Ljava/lang/Throwable |
一個方法的簽名爲“ (參數類型簽名) + 返回值類型簽名”,像上面方法:String getJavaInfo(String info, int index)的情況,所以它的簽名就應該是:(Ljava/lang/String;I)Ljava/lang/String; ,要注意不要漏掉分號。