Android中JNI入门(二) 之 Java与Native相互调用

时间:2024-05-19 18:21:14

我们在上一篇文章《Android中JNI入门(一) 之 初识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);
}

非常简单的修改,然后再重新编译程序,运行可见:

Android中JNI入门(二) 之 Java与Native相互调用

 

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便能查看到整个类里方法的签名,如图:

Android中JNI入门(二) 之 Java与Native相互调用

红框中便是上面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; ,要注意不要漏掉分号。

 

 

点击下载示例