Java -- 常用的JNI接口函数简介(一)

Java -- 常用的JNI接口函数简介(一)

 

JNI是连接Java代码和C/C++代码的桥梁。Android framework中,大量对某些C/C++开源库函数的调用都是通过JNI实现的。Java层中定义native方法,调用native方法就相当于调用了一个JNI函数,而JNI函数中再间接调用例如某个开源库提供的一个C函数。由此可知,framework中的JNI函数也只是一个间接封装,它接收从Java代码传入的参数列表,经过转换继续传递给某个C/C++实现的功能函数,处理完成后JNI函数可以将结果作为返回值,传递给Java层。

我们常见的JNI函数操作有:

  1. 将String字符串转化成char*
  2. 获取某个Java类的实例
  3. 操作某个Java类的字段
  4. 调用某个Java类的成员方法

下面一一做简要介绍,假定JNI代码都是C++实现。

 

一、字符串操作

 

JNI中要将String类型的字符串转换成char*型,需要调用GetStringUTFChars()方法:

GetStringUTFChars
 
const char * GetStringUTFChars(JNIEnv *env, jstring string,
 jboolean *isCopy);
 
Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding. This array is valid until it is released by ReleaseStringUTFChars().
 
If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy is made; or it is set to JNI_FALSE if no copy is made.

例如将ifname转换成char*:

static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
{
    int result;

    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
    result = ::dhcp_stop(nameStr);
    env->ReleaseStringUTFChars(ifname, nameStr);
    return (jboolean)(result == 0);
}

因为是指针操作,当我们确认某个char*类型不再使用时,需要手动释放内存,调用ReleaseStringUTFChars():

ReleaseStringUTFChars
 
void ReleaseStringUTFChars(JNIEnv *env, jstring string,
 const char *utf);
 
Informs the VM that the native code no longer needs access to utf. 
The utf argument is a pointer derived from string using GetStringUTFChars().

同理,我们也可以将char*类型字符串转换成String类型,在调用Java函数时,作为参数传递:

NewStringUTF
 
jstring NewStringUTF(JNIEnv *env, const char *bytes);
 
Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.

使用较为简单,就不列举示例了。

 

 

二、获取Java类的实例

 

在JNI中,我们可以通过一个Java类的全限定名来获取一个该类的类对象实例:

FindClass
 
jclass FindClass(JNIEnv *env, const char *name);

The name argument is a fully-qualified class name or an array type signature . For example, the fully-qualified class name for the java.lang.String class is:
                   "java/lang/String"



The array type signature of the array class java.lang.Object[] is:
                   "[Ljava/lang/Object;"


例如获取一个DhcpResults类对象:

jclass dhcpResultsClass = env->FindClass("android/net/DhcpResults");

如果没有找到,函数返回NULL。

当然,我们同样可以根据一个Java对象找到对应的类的实例对象:

GetObjectClass
 
jclass GetObjectClass(JNIEnv *env, jobject obj);

Returns the class of an object.

PARAMETERS:
 
env: the JNI interface pointer.
 
obj: a Java object (must not be NULL).
 
RETURNS:
 
Returns a Java class object.

还有判断是否为某类的对象、两对象是否相同的函数:

IsInstanceOf
 
jboolean IsInstanceOf(JNIEnv *env, jobject obj,
 jclass clazz);
 
Tests whether an object is an instance of a class.

PARAMETERS:
 
env: the JNI interface pointer.
 
obj: a Java object.
 
clazz: a Java class object.
 
RETURNS:
 
Returns JNI_TRUE if obj can be cast to clazz; otherwise, returns JNI_FALSE. A NULL object can be cast to any class.
-------------------------------------------------------------
IsSameObject
 
jboolean IsSameObject(JNIEnv *env, jobject ref1,
 jobject ref2);
 
Tests whether two references refer to the same Java object.

PARAMETERS:
 
env: the JNI interface pointer.
 
ref1: a Java object.
 
ref2: a Java object.
 
RETURNS:
 
Returns JNI_TRUE if ref1 and ref2 refer to the same Java object, or are both NULL; otherwise, returns JNI_FALSE.

参数含义都比较清晰,还有疑问的可以查看具体英文解释。另外,函数调用都比较简单,就不列举示例了。

 

三、操作某个Java类的字段

 

在JNI中,当我们需要操作一个Java类的字段时,首先需要得到一个jfieldID类型的值来表示这个字段,我们调用GetFieldID()来获取:

GetFieldID
 
jfieldID GetFieldID(JNIEnv *env, jclass clazz,
 const char *name, const char *sig);
 
Returns the field ID for an instance (nonstatic) field of a class. The field is specified by its name and signature. 
The Get<type>Field and Set<type>Field families of accessor functions use field IDs to retrieve object fields.
 
GetFieldID() causes an uninitialized class to be initialized.
 
GetFieldID() cannot be used to obtain the length field of an array. Use GetArrayLength() instead.



LINKAGE:

Index 94 in the JNIEnv interface function table.

PARAMETERS:
 
env: the JNI interface pointer.
 
clazz: a Java class object.
 
name: the field name in a 0-terminated modified UTF-8 string.
 
sig: the field signature in a 0-terminated modified UTF-8 string.
 
RETURNS:
 
Returns a field ID, or NULL if the operation fails.

参数env是JNI接口指针;clazz是该字段所属类的类实例,通过FindClass()获得;name是该字段在Java类中定义的名字;sig是该字段在Java类中定义的类型。
示例:

    jclass event_class =
            env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
    jfieldID type_field = env->GetFieldID(event_class, "type", "I");

获取内部类Event中一个名为type的int类型的字段。
得到了某个Java字段的jfieldID值后,我们可以通过JNI函数获取它的值,或者给它赋值:

Get<type>Field Routines
 
NativeType Get<type>Field(JNIEnv *env, jobject obj,
 jfieldID fieldID);
 
This family of accessor routines returns the value of an instance (nonstatic) field of an object. 
The field to access is specified by a field ID obtained by calling GetFieldID().
 
Set<type>Field Routines
 
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
 NativeType value);
 
This family of accessor routines sets the value of an instance (nonstatic) field of an object. 
The field to access is specified by a field ID obtained by calling GetFieldID().

<type>类型值要拿我们在Java中类定义该字段的类型来代替;如果是int,则type是Int;如果是boolean,则type是Boolean;依次类推,类型值首字母大写。

   

同理,获取Java类的某静态字段的操作跟上述介绍的获取非静态字段的方法基本类似。但有两点区别:1、由于非静态字段时属于某个对象的,所以进行赋值/取值操作时,函数接收的是jobject类型的实例对象,代表哪一个对象的字段值;而静态字段是属于类的,所以传递的是jclass对象,代表一个Java类的实例。2、操作静态字段的JNI函数在函数名中添加了static标识,以示区分。

下面列举出static字段操作的相关函数:

GetStaticFieldID
 
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,
 const char *name, const char *sig);
 
Returns the field ID for a static field of a class. The field is specified by its name and signature. 
The GetStatic<type>Field and SetStatic<type>Field families of accessor functions use field IDs to retrieve static fields.
 
GetStaticFieldID() causes an uninitialized class to be initialized.
GetStatic<type>Field Routines
 
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz,
 jfieldID fieldID);
 
This family of accessor routines returns the value of a static field of an object. 
The field to access is specified by a field ID, which is obtained by calling GetStaticFieldID().
SetStatic<type>Field Routines
 
void SetStatic<type>Field(JNIEnv *env, jclass clazz,
 jfieldID fieldID, NativeType value);
 
This family of accessor routines sets the value of a static field of an object. 
The field to access is specified by a field ID, which is obtained by calling GetStaticFieldID().

同理也有:

  
 

通过上述介绍的一系列函数,我们就可以在JNI中获取到Java的静态/非静态字段,并进行取值/赋值操作。

 

四、操作某个Java类的函数

 

先介绍操作非静态方法。通操作字段类似,要在JNI中操作Java的方法,首先也要获取某个Java方法的jmethodID类型值。在JNI中,一个jmethodID值,就代表一个Java类的方法。

调用GetMethodID()获取jmethodID:

GetMethodID
 
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
 const char *name, const char *sig);
 
Returns the method ID for an instance (nonstatic) method of a class or interface. 
The method may be defined in one of the clazz’s superclasses and inherited by clazz.
The method is determined by its name and signature.
 
GetMethodID() causes an uninitialized class to be initialized.
 
To obtain the method ID of a constructor, supply <init> as the method name and void (V) as the return type.

各参数含义:

env: the JNI interface pointer.
 
clazz: a Java class object.

name: the method name in a 0-terminated modified UTF-8 string.
 
sig: the method signature in 0-terminated modified UTF-8 string.

由于Java允许函数重载,所以要依赖函数签名才能唯一定位到Java函数。name是Java中函数定义的函数名;sig是Java中函数定义的参数列表和返回值信息,函数签名的写法在第一篇JNI博客中介绍过。列出一些示例:

static struct fieldIds {
    jmethodID clear;
    jmethodID setIpAddress;
    jmethodID setGateway;
    jmethodID addDns;
    jmethodID setDomains;
    jmethodID setServerAddress;
    jmethodID setLeaseDuration;
    jmethodID setVendorInfo;
} dhcpResultsFieldIds;
static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
                                         const char* method_signature) {
    jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s", method_name);
    return res;
}
int register_android_net_NetworkUtils(JNIEnv* env)
{
    jclass dhcpResultsClass = FindClassOrDie(env, "android/net/DhcpResults");

    dhcpResultsFieldIds.clear = GetMethodIDOrDie(env, dhcpResultsClass, "clear", "()V");
    dhcpResultsFieldIds.setIpAddress =GetMethodIDOrDie(env, dhcpResultsClass, "setIpAddress",
            "(Ljava/lang/String;I)Z");
    dhcpResultsFieldIds.setGateway = GetMethodIDOrDie(env, dhcpResultsClass, "setGateway",
            "(Ljava/lang/String;)Z");
    dhcpResultsFieldIds.addDns = GetMethodIDOrDie(env, dhcpResultsClass, "addDns",
            "(Ljava/lang/String;)Z");
    dhcpResultsFieldIds.setDomains = GetMethodIDOrDie(env, dhcpResultsClass, "setDomains",
            "(Ljava/lang/String;)V");
    dhcpResultsFieldIds.setServerAddress = GetMethodIDOrDie(env, dhcpResultsClass,
            "setServerAddress", "(Ljava/lang/String;)Z");
    dhcpResultsFieldIds.setLeaseDuration = GetMethodIDOrDie(env, dhcpResultsClass,
            "setLeaseDuration", "(I)V");
    dhcpResultsFieldIds.setVendorInfo = GetMethodIDOrDie(env, dhcpResultsClass, "setVendorInfo",
            "(Ljava/lang/String;)V");

    return RegisterMethodsOrDie(env, NETUTILS_PKG_NAME, gNetworkUtilMethods,
                                NELEM(gNetworkUtilMethods));
}

得到某个Java函数的jmethodID值是操作的前提。根据参数传递情况的不同,在某个Java对象上调用成员函数有三种方式:

Call<type>Method Routines
 Call<type>MethodA Routines
 Call<type>MethodV Routines
 
NativeType Call<type>Method(JNIEnv *env, jobject obj,
 jmethodID methodID, ...);
 
NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
 jmethodID methodID, const jvalue *args);
 
NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
 jmethodID methodID, va_list args);
 
Methods from these three families of operations are used to call a Java instance method from a native method.
They only differ in their mechanism for passing parameters to the methods that they call.
 
These families of operations invoke an instance (nonstatic) method on a Java object, according to the specified method ID. 
The methodID argument must be obtained by calling GetMethodID().
 
When these functions are used to call private methods and constructors, the method ID must be derived from the real class of obj, 
not from one of its superclasses.
 
Call<type>Method Routines
 
Programmers place all arguments that are to be passed to the method immediately following the methodID argument. 
The Call<type>Method routine accepts these arguments and passes them to the Java method that the programmer wishes to invoke.
 
Call<type>MethodA Routines
 
Programmers place all arguments to the method in an args array of jvalues that immediately follows the methodID argument. 
The Call<type>MethodA routine accepts the arguments in this array, and, in turn, passes them to the Java method that the programmer wishes to invoke.

第一种调用方式是最常用的方式,参数直接一一传递,而不通过jvalue数组或va_list。我在网络模块相关的JNI调用中,看见的调用方式都是第一种。第二种很少见到,我特意搜了一下,后面列出一个代码中的示例供大家参考;最后一种,我还没见到过。。。下面看下各个参数的含义:

PARAMETERS:
 
env: the JNI interface pointer.
 
obj: a Java object.
 
methodID: a method ID.
 
Additional Parameter for Call<type>Method Routines:
 
arguments to the Java method.
 
Additional Parameter for Call<type>MethodA Routines:
 
args: an array of arguments.
 
Additional Parameter for Call<type>MethodV Routines:
 
args: a va_list of arguments.

对于非静态函数,参数列表中的methodID是调用GetMethodID()函数返回的;参数列表的传递要跟Java中函数定义的参数列表一致。

用你调用的Java方法的返回值类型替换<type>后,有如下函数名列表:

下面列出一些JNI中调用Java函数的例子,在dhcpResults(DhcpResults)这个对象调用Java函数,给其赋值:

static jboolean android_net_utils_getDhcpResults(JNIEnv* env, jobject clazz, jstring ifname,
        jobject dhcpResults)
{
...
if (result == 0) {
        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear);

        // set the linkAddress
        // dhcpResults->addLinkAddress(inetAddress, prefixLength)
        result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.setIpAddress,
                env->NewStringUTF(ipaddr), prefixLength);
    }

    if (result == 0) {
        // set the gateway
        result = env->CallBooleanMethod(dhcpResults,
                dhcpResultsFieldIds.setGateway, env->NewStringUTF(gateway));
    }

    if (result == 0) {
        // dhcpResults->addDns(new InetAddress(dns1))
        result = env->CallBooleanMethod(dhcpResults,
                dhcpResultsFieldIds.addDns, env->NewStringUTF(dns1));
    }

    if (result == 0) {
        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setDomains,
                env->NewStringUTF(domains));

        result = env->CallBooleanMethod(dhcpResults,
                dhcpResultsFieldIds.addDns, env->NewStringUTF(dns2));

        if (result == 0) {
            result = env->CallBooleanMethod(dhcpResults,
                    dhcpResultsFieldIds.addDns, env->NewStringUTF(dns3));
            if (result == 0) {
                result = env->CallBooleanMethod(dhcpResults,
                        dhcpResultsFieldIds.addDns, env->NewStringUTF(dns4));
            }
        }
    }
...
}

Call<type>MethodA调用方式的示例:

static void android_animation_PropertyValuesHolder_callMultipleFloatMethod(
        JNIEnv* env, jclass pvhObject, jobject target, jlong methodID, jfloatArray arg)
{
    jsize parameterCount = env->GetArrayLength(arg);
    jfloat *floatValues = env->GetFloatArrayElements(arg, NULL);
    jvalue* values = new jvalue[parameterCount];
    for (int i = 0; i < parameterCount; i++) {
        values[i].f = floatValues[i];
    }
    env->CallVoidMethodA(target, reinterpret_cast<jmethodID>(methodID), values);
    delete[] values;
    env->ReleaseFloatArrayElements(arg, floatValues, JNI_ABORT);
}

类似地,对于在JNI中调用Java的静态方法,有一套类似的JNI函数,调用方法跟调用非静态函数的过程基本一致。先获取jmethodID:

GetStaticMethodID
 
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,
 const char *name, const char *sig);
 
Returns the method ID for a static method of a class. The method is specified by its name and signature.
 
GetStaticMethodID() causes an uninitialized class to be initialized.

PARAMETERS:
 
env: the JNI interface pointer.
 
clazz: a Java class object.
 
name: the static method name in a 0-terminated modified UTF-8 string.
 
sig: the method signature in a 0-terminated modified UTF-8 string.
 
RETURNS:
 
Returns a method ID, or NULL if the operation fails.
 
CallStatic<type>Method Routines
 CallStatic<type>MethodA Routines
 CallStatic<type>MethodV Routines
 
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz,
 jmethodID methodID, ...);
 
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz,
 jmethodID methodID, jvalue *args);
 
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz,
 jmethodID methodID, va_list args);
 
This family of operations invokes a static method on a Java object, according to the specified method ID. 
The methodID argument must be obtained by calling GetStaticMethodID().
 
The method ID must be derived from clazz, not from one of its superclasses.
 
CallStatic<type>Method Routines
 
Programmers should place all arguments that are to be passed to the method immediately following the methodID argument. 
The CallStatic<type>Method routine accepts these arguments and passes them to the Java method that the programmer wishes to invoke.
 
CallStatic<type>MethodA Routines
 
Programmers should place all arguments to the method in an args array of jvalues that immediately follows the methodID argument. 
The CallStaticMethodA routine accepts the arguments in this array, and, in turn, passes them to the Java method that the programmer wishes to invoke.
 
CallStatic<type>MethodV Routines
 
Programmers should place all arguments to the method in an args argument of type va_list that immediately follows the methodID argument. 
The CallStaticMethodV routine accepts the arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.

相比调用非静态函数,函数名添加了Static标识。参数列表的含义为:

PARAMETERS:
 
env: the JNI interface pointer.
 
clazz: a Java class object.
 
methodID: a static method ID.
 
Additional Parameter for CallStatic<type>Method Routines:
 
arguments to the static method.
 
Additional Parameter for CallStatic<type>MethodA Routines:
 
args: an array of arguments.
 
Additional Parameter for CallStatic<type>MethodV Routines:
 
args: a va_list of arguments.
 
RETURNS:
 
Returns the result of calling the static Java method.

同理,用Java中函数的返回值类型替换<type>,有如下函数列表:

 

特殊地,要注意调用非静态函数和静态函数时,一个传递的是Java object,一个是Java class object。

我们知道,在Java函数中,有一类特殊的成员函数:构造函数;当我们要在JNI中调用Java类的构造函数,创建一个对象该怎么做呢?
有前面的内容可知,我们如果要获取到一个Java函数对应的jmethodID值时,需要传入函数名和函数的签名信息。对于构造函数,在JNI中它有一个特殊的函数名字:"<init>"。当我们需要获取某个构造函数的jmethodID值时,函数名的参数填入"<init>"即可;函数签名信息的填写规则则跟普通函数一样:

    jclass event_class =
            env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
    jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");

示例是一个获取无参构造函数jmethodID值的例子。如果要在JNI中创建一个Java类的实例对象,要调用env->NewObject()函数:

jobject NewObject(JNIEnv *env, jclass clazz,
 jmethodID methodID, ...);
NewObject
 
Programmers place all arguments that are to be passed to the constructor immediately following the methodID argument. 
NewObject() accepts these arguments and passes them to the Java method that the programmer wishes to invoke.
 
PARAMETERS:
 
env: the JNI interface pointer.
 
clazz: a Java class object.
 
methodID: the method ID of the constructor.
 
Additional Parameter for NewObject:
 
arguments to the constructor.

该函数对应的还有NewObjectA、NewObjectV两种形式,就不介绍了。再举一个JNI调用Java无参构造函数创建实例对象的例子:

    jclass event_class =
            env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
    jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");
        jobject event_object = env->NewObject(event_class, event_ctor);

通常jfieldID、jmethodID类型值的获取都需要占用一定的系统资源,所以频繁地获取是不可取的。我们通常会把这两类值保存为static的,以达到一次获取、多次使用的目的,减少不必要的系统开销。

当然,JNI函数常用的并不止文章所介绍的,其他的常用函数会陆续在博客中介绍。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值