上篇文章 JNI访问Java对象的成员 介绍了如何在JNI层回调Java对象的成员(变量和方法),这篇文章是上篇文章 的姊妹篇,介绍在JNI层如何回调Java类的静态成员(变量和方法)。
首先呢,还是需要做一些准备工作,先完成动态注册的代码。
如果你对动态注册的代码还不熟悉,可以通过JNI函数动态注册和JNI函数动态注册进阶学习。
首先在Java类中加载动态库,然后调用 native
方法,代码如下
package com.umx.ndkdemo; public class Person { private static String mName = "Nobody"; static { System.loadLibrary("person_jni"); } public native void native_hello(); public static void sayHello() { System.out.println(mName + ": Hello World!"); } public static void main(String[] args) { new Person().native_hello(); } } 复制代码
然后在JNI层进行动态注册
#include "jni.h" static void com_umx_ndkdemo_Person_native_hello(JNIEnv *env, jobject thiz) { } static const JNINativeMethod nativeMethods[] = { {"native_hello", "()V", (void *) com_umx_ndkdemo_Person_native_hello} }; jint JNI_OnLoad(JavaVM * vm, void * reserved) { jint jni_version = -1; JNIEnv* env = NULL; if (vm->GetEnv((void **)&env, JNI_VERSION_1_1) == JNI_OK) { jclass clazz_Person = env->FindClass("com/umx/ndkdemo/Person"); if (env->RegisterNatives(clazz_Person, nativeMethods, sizeof(nativeMethods) / sizeof(nativeMethods[0])) == JNI_OK) { jni_version = JNI_VERSION_1_6; } } return jni_version; } 复制代码
com_umx_ndkdemo_Person_native_hello
就是要实现的方法,在这个方法中将会做三件事情
Hello.java
类的静态变量 mName
的值。 Hello.java
类的静态变量 mName
的值。 Hello.java
的静态方法 sayHello
。
首先实现获取 Hello.java
静态变量 mName
的值
#include <android/log.h> static void com_umx_ndkdemo_Person_native_hello(JNIEnv *env, jobject thiz) { // 获取Class对象 jclass clazz_Person = env->FindClass("com/umx/ndkdemo/Person"); // 从Class对象中获取mName字段 jfieldID fieldID_mName = env->GetStaticFieldID(clazz_Person, "mName", "Ljava/lang/String;"); // 获取静态变量的值 jstring mName = (jstring) env->GetStaticObjectField(clazz_Person, fieldID_mName); // 打印输出 __android_log_print(ANDROID_LOG_INFO, "bxll", "name = %/n", mName); } 复制代码
和Java反射类似,使用JNI获取Java类的静态变量的步骤如下
在例子中是通过 FindClass
函数来获取Class对象的,函数原型如下
jclass FindClass(JNIEnv *env, const char *name); 复制代码
参数 const char * name
可以是全限定的Class名,或者是一个数组类型的签名。
String
类,全限定Class名为 java.lang.String
,但是由于点号在JNI中有特殊意义,因此使用斜线来代替点号,全限定Class名为 java/lang/String
。 String[]
,那么参数就要为数组的类型签名 [Ljava/lang/String;
。 如果还不了解数组的类型的签名是什么,可能参数JNI函数动态注册进阶。
在例子中,是通过 GetStaticFieldID
函数来获取Class对象的静态字段,函数原型如下
jfieldID GetStaticFieldID (JNIEnv *env, jclass clazz, const char *name, const char *sig); 复制代码
参数
jclass clazz
: Class对象,通过 FindClass
函数获取。 const char *name
: Class对象的字段名,也就是Java类的静态变量名。 const char *sig
: 静态变量的类型签名。 如果还不了解数组的类型的签名是什么,可能参数JNI函数动态注册进阶。
根据Java类的静态变量的类型的不同,在JNI中有不同的方法来获取静态变量的值,但是基本形式如下
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID); 复制代码
这类函数可以是为两类,一类是处理Java的8种基本类型,一类是处理所有的Java引用类型,如下表
方法名 | 返回值 |
---|---|
GetStaticBooleanField | jboolean |
GetStaticByteField | jbyte |
GetStaticCharField | jchar |
GetStaticShortField | jshort |
GetStaticIntField | jint |
GetStaticLongField | jlong |
GetStaticFloatField | jfloat |
GetStaticDoubleField | jdouble |
GetStaticObjectField | jobject |
前8项是处理Java对应的8种基本类型,最后一项是处理其它所有的Java类型。
例如,对于Java类的 int
类型的静态变量,是用如下函数获取
jint GetStaticIntField(JNIEnv *env, jclass clazz, jfieldID fieldID); 复制代码
而对于Java的 String
类型的静态变量,是用如下函数获取的
jstring GetStaticObjectField(JNIEnv *env, jclass clazz, jfieldID fieldID); 复制代码
现在来实现设置静态变量的值
static void com_umx_ndkdemo_Person_native_hello(JNIEnv *env, jobject thiz) { // 获取Class对象 jclass clazz_Person = env->FindClass("com/umx/ndkdemo/Person"); // 获取字段 jfieldID fieldID_mName = env->GetStaticFieldID(clazz_Person, "mName", "Ljava/lang/String;"); // 设置字段的值 jstring name = env->NewStringUTF("David"); env->SetStaticObjectField(clazz_Person, fieldID_mName, name); } 复制代码
设置Java类静态变量的值有以下几步
前两步与已经介绍过,直接说明第三步是如何使用的
根据Java类的静态变量的类型的不同,在JNI中有不同的方法来获设置态变量的值,但是基本形式如下
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value); 复制代码
其中最后一个参数指的是要给Java类的静态变量设置的值,它的类型会根据要设置的静态变量的类型的不同而不同,例如,要给 int
类型的静态变量设置值,那么这里的 NativeType
就对应 jint
。
JNI处理Java类型的方式分为基本类型(8种)的引用类型,因此这里对应的JNI方法就有9种
方法名 | NativeType |
---|---|
SetStaticBooleanField | jboolean |
SetStaticByteField | jbyte |
SetStaticCharField | jchar |
SetStaticShortField | jshort |
SetStaticIntField | jint |
SetStaticLongField | jlong |
SetStaticFloatField | jfloat |
SetStaticDoubleField | jdouble |
SetStaticObjectField | jobject |
前8项就是用来设置Java的基本类型的,最后一项就是用来处理Java引用类型的。
例如,如果要给Java类的 int
类型的静态变量设置值,那么就要调用如下函数
void SetStaticIntField(JNIEnv *env, jclass clazz, jfieldID fieldID, jint value); 复制代码
例如,如果要给Java类的 String
类型的变量设置值,那么就要调用如下的函数
void SetStaticObjectField(JNIEnv *env, jclass clazz, jfieldID fieldID, jobject value) 复制代码
static void com_umx_ndkdemo_Person_native_hello(JNIEnv *env, jobject thiz) { jclass clazz_Person = env->FindClass("com/umx/ndkdemo/Person"); if (clazz_Person == NULL) { return; } jfieldID fieldID_mName = env->GetStaticFieldID(clazz_Person, "mName", "Ljava/lang/String;"); if (fieldID_mName == NULL) { return; } jstring mName = (jstring) env->GetStaticObjectField(clazz_Person, fieldID_mName); __android_log_print(ANDROID_LOG_INFO, "david", "name = %/n", mName); jstring name = env->NewStringUTF("David"); env->SetStaticObjectField(clazz_Person, fieldID_mName, name); jmethodID methodID_sayHello = env->GetStaticMethodID(clazz_Person, "sayHello", "()V"); env->CallStaticVoidMethod(clazz_Person, methodID_sayHello); // 删除局部引用(可选) env->DeleteLocalRef(name); env->DeleteLocalRef(mName); env->DeleteLocalRef(clazz_Person); } 复制代码
在这个完整实现中,加入了对 jclass
和 jmethodID
的判空,以及手动删除局部引用的操作。
这篇文章其实和上篇文章非常类似,也非常好理解,只要搞清楚了流程,就可以非常熟练的使用了。
其实还有一个非常有意思的事情,如何访问(获取/设置)Java的数组类型的静态变量?恩,这个问题留到下一篇文章来分析。