上篇文章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
。
访问Java类的静态变量
获取静态变量的值
首先实现获取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类的静态变量的步骤如下
- 获取Class对象
- 获取Class对象的字段名,也就是静态变量名
- 通过Class对象和字段名,获取静态变量的值
FindClass
在例子中是通过FindClass
函数来获取Class对象的,函数原型如下
jclass FindClass(JNIEnv *env, const char *name);
复制代码
参数const char * name
可以是全限定的Class名,或者是一个数组类型的签名。
- 对于Java的
String
类,全限定Class名为java.lang.String
,但是由于点号在JNI中有特殊意义,因此使用斜线来代替点号,全限定Class名为java/lang/String
。 - 对于Java的数组类,例如
String[]
,那么参数就要为数组的类型签名[Ljava/lang/String;
。
如果还不了解数组的类型的签名是什么,可能参数JNI函数动态注册进阶。
GetStaticFieldID
在例子中,是通过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函数动态注册进阶。
GetStatic<type>Field
根据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类的Class对象
- 获取静态变量的字段
- 通过Class对象和字符,设置静态变量的值
前两步与已经介绍过,直接说明第三步是如何使用的
SetStatic<type>Field
根据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的数组类型的静态变量?恩,这个问题留到下一篇文章来分析。
作者:不惜留恋_
链接:https://juejin.im/post/5d29fd58f265da1b7b31b9e1
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。