Android中JNI&NDK入門(二) 之 Java與Native相互調用

我們在上一篇文章《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; ,要注意不要漏掉分號。

 

 

點擊下載示例

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章