JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性

 JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性

  在前面的章节JNI/NDK入门指南之JNI访问数组中讲解了JNI对基本类型数组和各种引用类型数组的访问。今天我们继续向JNI的知识海洋进军讲解C/C++通过JNI访问Java实例属性和类静态属性的处理。本章内容有点多哦!



前言

通过前面的正佳我们知道了如何通过 JNI 函数来访问Native C/C++中的基本数据类型、字符串和数组这些数据类型。接下来我们接着学习C/C++本地代码如何通过JNI访问Java类中任意对象的属性交互。比如本地代码调用 Java 层某个对象的变量或者Java类的静态变量。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过【类名.变量名】来访问。实例变量也称为成员变量(属性),每个实例都拥有一份实例变量数据的拷贝,它们之间修改后的数据互不影响。下面让我们从JNI的访问对象操作函数开始,一步步带领读者攻破C/C++通过JNI访问Java实例变量和静态变量。好了,开车了。



一. 初探JNI访问Java实例属性处理函数

好了有了前面知识的铺垫,下面让我们来看看JNIEnv为我们提供了那些常见Java对象属性处理函数。

1.1 GetFieldID

函数原型: jfieldID GetFieldID (JNIEnv *env, jclass clazz, const char *name, const char *sig)
函数功能: 返回类的实例(非静态)域的属性 ID。该域由其名称及签名指定。访问属性系列函数Get<PrimitiveType>Field 及 Set<PrimitiveType>Field会使用。GetFieldID() 不能用于获取数组的长度域。应使用GetArrayLength()。
参数

  • env: JNIEnv接口指针
  • clazz: Java类对象,不是Java类的实例jobject。类对象,就是用来描述这种类,都有什么属性,什么方法的。所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法等等。
  • name: 该属性在类中的名称
  • sig:该属性的域签名,关于域签名可以参见章节JNI/NDK入门指南之JNI数据类型,描述符详解介绍

返回值:属性ID。如果操作失败,则返回NULL。
抛出:

  • NoSuchFieldError:如果找不到指定的域
  • ExceptionInInitializerError:如果由于异常而导致类初始化程序失败
  • OutOfMemoryError:如果系统内存不足

1.2 Get<PrimitiveType>Field 函数集

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: NativeType Get<PrimitiveType>Field (JNIEnv*env, jobject obj, jfieldID fieldID)
函数功能: 该函数集用于返回Java类的实例(非静态)域的值。要访问的域由通过调用GetFieldID() 而得到的域 ID 指定。
使用说明: 在下表中会将特定的基本类型数组构造函数及其返回值一一对应。在实际应用中把PrimitiveType替换为某个实际的基本类型数据类型,然后再将NativeType替换成对应的JNI Native Type即可。譬如我们以int型来说明,那么其对应的函数为:

jint GetIntField(JNIEnv * env, jobject obj, jfieldID fieldID);

参数:

  • env: JNIEnv接口指针
  • obj:Java类实例或者Java对象
  • fieldID:有效的域 ID

返回值:属性的内容。

函数集表格:

Get<PrimitiveType>函数名 NativeType JNI本地类型
GetObjectField() jobject
GetBooleanField() jboolean
GetByteField() jbyte
GetCharField() jchar
GetShortField() jshort
GetIntField() jint
GetLongField() jlong
GetFloatField() jfloat
GetDoubleField() jdouble

1.3 Set<PrimitiveType>Field 函数集

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: void Set<PrimitiveType>Field (JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value)
函数功能: 该函数集用于设置对象的实例(非静态)属性的值。要访问的属性由通过调用GetFieldID() 而得到的属性 ID指定。
使用说明: 在下表中会将特定的基本类型数组构造函数及其返回值一一对应。在实际应用中把PrimitiveType替换为某个实际的基本类型数据类型,然后再将NativeType替换成对应的JNI Native Type即可。譬如我们以int型来说明,那么其对应的函数为:

void SetIntField(JNIEnv * env, jobject obj, jfieldID fieldID, jint value);

参数:

  • env: JNIEnv接口指针
  • obj: Java对象或者Java类实例(不能为NULL)
  • value: 传递给域的新值

函数集表格:

Set<PrimitiveType>函数名 NativeType JNI本地类型
SetObjectField() jobject
SetBooleanField() jboolean
SetByteField() jbyte
SetCharField() jchar
SetShortField() jshort
SetIntField() jint
SetLongField() jlong
SetFloatField() jfloat
SetDoubleField() jdouble


二. 初探JNI访问Java类对象属性处理函数

在前面的章节我们讲解了访问和修改Java对象属性的处理函数,下面让我们来看看JNIEnv为我们提供了那些常见Java类对象属性处理函数。

2.1 GetStaticFieldID

函数原型: jfieldID GetStaticFieldID (JNIEnv *env, jclass clazz, const char *name, const char *sig)
函数功能: 返回类对象(静态)域的属性 ID。该域由其名称及签名指定。访问属性系列函数GetStatic<PrimitiveType>Field 及 SetStatic<PrimitiveType>Field会使用。
参数

  • env: JNIEnv接口指针
  • clazz: Java类对象,不是Java类的实例jobject。类对象,就是用来描述这种类,都有什么属性,什么方法的。所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法等等。
  • name: 该属性在类中的名称
  • sig:该属性的域签名,关于域签名可以参见章节JNI/NDK入门指南之JNI数据类型,描述符详解介绍

返回值:属性ID。如果操作失败,则返回NULL。
抛出:

  • NoSuchFieldError:如果找不到指定的域
  • ExceptionInInitializerError:如果由于异常而导致类初始化程序失败
  • OutOfMemoryError:如果系统内存不足

2.2 GetStatic<PrimitiveType>Field 函数集

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: NativeType GetStatic<PrimitiveType>Field (JNIEnv*env, jclass clazz, jfieldID fieldID)
函数功能: 该函数集用于返回Java类对象(静态)域的值。要访问的域由通过调用GetStaticFieldID() 而得到的域 ID 指定。
使用说明: 在下表中会将特定的基本类型数组构造函数及其返回值一一对应。在实际应用中把PrimitiveType替换为某个实际的基本类型数据类型,然后再将NativeType替换成对应的JNI Native Type即可。譬如我们以int型来说明,那么其对应的函数为:

jint GetStaticIntField(JNIEnv * env, jclass clazz, jfieldID fieldID);

参数:

  • env: JNIEnv接口指针
  • clazz:Java类对象
  • fieldID:有效的域 ID

返回值:静态属性的内容。

函数集表格:

GetStatic<PrimitiveType>函数名 NativeType JNI本地类型
GetStaticObjectField() jobject
GetStaticBooleanField() jboolean
GetStaticByteField() jbyte
GetStaticCharField() jchar
GetStaticShortField() jshort
GetStaticIntField() jint
GetStaticLongField() jlong
GetStaticFloatField() jfloat
GetStaticDoubleField() jdouble

2.3 SetStatic<PrimitiveType>Field 函数集

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: void SetStatic<PrimitiveType>Field (JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value)
函数功能: 该函数集用于设置Java类对象(静态)属性的值。要访问的属性由通过调用GetStaticFieldID() 而得到的属性 ID指定。
使用说明: 在下表中会将特定的基本类型数组构造函数及其返回值一一对应。在实际应用中把PrimitiveType替换为某个实际的基本类型数据类型,然后再将NativeType替换成对应的JNI Native Type即可。譬如我们以int型来说明,那么其对应的函数为:

void SetStaticIntField(JNIEnv * env, jobject obj, jfieldID fieldID, jint value);

参数:

  • env: JNIEnv接口指针
  • clazz: Java类对象(不能为NULL)
  • value: 传递给域的新值

函数集表格:

SetStatic<PrimitiveType>函数名 NativeType JNI本地类型
SetStaticObjectField() jobject
SetStaticBooleanField() jboolean
SetStaticByteField() jbyte
SetStaticCharField() jchar
SetStaticShortField() jshort
SetStaticIntField() jint
SetStaticLongField() jlong
SetStaticFloatField() jfloat
SetStaticDoubleField() jdouble

可以看出它们与实例属性的唯一区别在于第二个参数jclass classzz代表的是类引用,而不是类实例。



三. JNI访问Java实例属性和类静态属性实战分析

前面的章节,我们将JNI访问Java实例属性和类静态属性中有关访问函数集,一网打净了(当然是夸张说法了)。说得再多不练,都是纸上谈兵,下面我们来实战一把,跟紧我要开战了,可别走丢了。

3.1 JNI访问和修改Java实例属性

好了前面的理论知识我们已经OK了,那么接下来都懂的必须来点实战的东西,不能光说不练。让我带领读者来看看怎么通过JNI访问和修改Java实例属性。
Java端代码:
Java实例所属类JNIFieldClass.java代码:

package com.pax.jni.field;

public class JNIFieldClass {

    private String mString = "Hello JNI, this is normal string !";

    private int mInt = 1;
    @Override
    public String toString() {
        return "JNIFieldClass [mString=" + mString + ", mInt=" + mInt + "]";
    }
}

Java端Native方法定义JNIAccessFieldManager.java代码:

package com.pax.jni.field;

/***
 * 
 * C/C++访问Java类的实例变量和静态变量
 * 
 */
public class JNIAccessFieldManager {

    public native void accessInstanceFiled(JNIFieldClass mJniFieldClass);
    static {
        System.loadLibrary("accesfield");// 加载so库
    }
}

Java端测试代码:

    private void operateInstanceFiled(){
           JNIFieldClass mJniFieldClass = new JNIFieldClass();
           Log.e("ACCESS_FIELD","operateInstanceFiled before : " + mJniFieldClass.toString());
           JNIAccessFieldManager mAccessFieldManager = new JNIAccessFieldManager();
           mAccessFieldManager.accessInstanceFiled(mJniFieldClass);
           Log.e("ACCESS_FIELD","operateInstanceFiled after : " + mJniFieldClass.toString());
    }

JNI端代码:
Java中Native方法对应com_pax_jni_field_JNIAccessFieldManager.h代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_pax_jni_field_JNIAccessFieldManager */

#ifndef _Included_com_pax_jni_field_JNIAccessFieldManager
#define _Included_com_pax_jni_field_JNIAccessFieldManager
#ifdef __cplusplus
extern "C" {
#endif


/*
 * Class:     com_pax_jni_field_JNIAccessFieldManager
 * Method:    accessInstanceFiled
 * Signature: (Lcom/pax/jni/field/JNIFieldClass;)V
 */
JNIEXPORT void JNICALL Java_com_pax_jni_field_JNIAccessFieldManager_accessInstanceFiled
  (JNIEnv *, jobject, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_pax_jni_field_JNIAccessFieldManager.cpp代码如下:

#include "com_pax_jni_field_JNIAccessFieldManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <errno.h>

#define TAG "ACCESS_FIELD"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)

//将jstring转换成char *
char* jstringToNative(JNIEnv *env, jstring jstr)
{
	if ((env)->ExceptionCheck() == JNI_TRUE || jstr == NULL)
	{
		(env)->ExceptionDescribe();
		(env)->ExceptionClear();
		//printf("jstringToNative函数转换时,传入的参数str为空");
		return NULL;
	} 
 
	jbyteArray bytes = 0; 
	jthrowable exc; 
	char *result = 0; 
	if ((env)->EnsureLocalCapacity(2) < 0) 
	{ 
		return 0; /* out of memory error */ 
	} 
	jclass jcls_str = (env)->FindClass("java/lang/String"); 
	jmethodID MID_String_getBytes = (env)->GetMethodID(jcls_str, "getBytes", "()[B"); 
 
	bytes = (jbyteArray)(env)->CallObjectMethod(jstr, MID_String_getBytes); 
	exc = (env)->ExceptionOccurred(); 
	if (!exc) 
	{ 
		jint len = (env)->GetArrayLength( bytes); 
		result = (char *)malloc(len + 1); 
		if (result == 0) 
		{ 
			//JNU_ThrowByName( "java/lang/OutOfMemoryError", 	0); 
			(env)->DeleteLocalRef(bytes); 
			return 0; 
		} 
		(env)->GetByteArrayRegion(bytes, 0, len, (jbyte *)result); 
		result[len] = 0; /* NULL-terminate */ 
	} 
	else 
	{ 
		(env)->DeleteLocalRef( exc); 
	} 
	(env)->DeleteLocalRef( bytes); 
	return (char*)result; 

} 

//将char *  转换成 jstring
jstring nativeTojstring( JNIEnv* env,const char* str )
{	
	//定义java String类 strClass
	jclass strClass = (env)->FindClass("java/lang/String");
	//获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
	jmethodID ctorID = (env)->GetMethodID( strClass, "<init>", "([BLjava/lang/String;)V");
	//建立byte数组
	jbyteArray bytes = (env)->NewByteArray( (jsize)strlen(str));
	//将char* 转换为byte数组
	(env)->SetByteArrayRegion( bytes, 0, (jsize)strlen(str), (jbyte*)str);
	//设置String, 保存语言类型,用于byte数组转换至String时的参数
	jstring encoding = (env)->NewStringUTF( "utf-8"); 
	//将byte数组转换为java String,并输出
	return (jstring)(env)->NewObject( strClass, ctorID, bytes, encoding); 
}

/*
 * Class:     com_pax_jni_field_JNIAccessFieldManager
 * Method:    accessInstanceFiled
 * Signature: (Lcom/pax/jni/field/JNIFieldClass;)V
 */
JNIEXPORT void JNICALL Java_com_pax_jni_field_JNIAccessFieldManager_accessInstanceFiled
  (JNIEnv * env, jobject object, jobject object_in)
{
	jclass clazz;//JNIFieldClass类引用
	jfieldID mString_fieldID;//JNIFieldClass类对象变量mString属性ID
	jfieldID mInt_fieldID;//JNIFieldClass类对象变量mInt属性ID

	/******获取JNIFieldClass类实例变量mString的值并修改********/
	//1.通过JNIFieldClass类实例object_in获取Class的引用
	clazz = env->GetObjectClass(object_in);
	if(clazz == NULL)
	{
		LOGE(TAG,"GetObjectClass failed\n");
		return;
	}

	//2.获取JNIFieldClass类的实例变量mString的属性ID
	//其中第二参数是变量的名称,第三个参数是变量类型的描述符
	mString_fieldID = env->GetFieldID(clazz, "mString",  "Ljava/lang/String;");
	if(mString_fieldID == NULL)
	{
		LOGE(TAG,"GetFieldID  mString failed\n");
		return;		
	}

	//3.获取JNIFieldClass类实例变量mString的值,并打印出来
	jstring j_string = (jstring)env->GetObjectField(object_in, mString_fieldID);
	char * buf = jstringToNative(env, j_string);
	LOGE(TAG,"object_in.mString : %s\n", buf);
	free(buf);


	//4.修改类实例变量mStriing的值
	char * buf_out = "Hello Java, I am JNI!";
	env->SetObjectField(object_in, mString_fieldID, nativeTojstring(env, buf_out));

	//5.释放局部引用
	env->DeleteLocalRef(j_string);

	/******获取JNIFieldClass类实例int型变量mInt的值并修改********/
	//6.获取JNIFieldClass类实例int型变量mString的属性ID
	mInt_fieldID = env->GetFieldID(clazz, "mInt", "I");
	if(mInt_fieldID == NULL)
	{
		LOGE(TAG,"GetFieldID  mInt failed\n");
		return;			
	}

	//7.获取JNIFieldClass实例变量mInt的值
	jint mInt = env->GetIntField(object_in, mInt_fieldID);
	LOGE(TAG,"object_in.mInt : %d\n", mInt);


	//8.修改JNIFieldClass实例变量mInt的值
	env->SetIntField(object_in, mInt_fieldID, 100);

	//9.删除局部引用,即对JNIFieldClass的类引用
	env->DeleteLocalRef(clazz);	
}

运行演示:

λ adb logcat  -s  ACCESS_FIELD
--------- beginning of main
--------- beginning of system
12-26 08:22:33.111  8575  8575 E ACCESS_FIELD: operateInstanceFiled before : JNIFieldClass [mString=Hello JNI, this is normal string !, mInt=1]
12-26 08:22:33.116  8575  8575 I ACCESS_FIELD: object_in.mString : Hello JNI, this is normal string !
12-26 08:22:33.117  8575  8575 I ACCESS_FIELD: object_in.mInt : 1
12-26 08:22:33.117  8575  8575 E ACCESS_FIELD: operateInstanceFiled after : JNIFieldClass [mString=Hello Java, I am JNI!, mInt=100]

案例分析:
在本例中,Java类JNIAccessFieldManager中定义了一个accessInstanceFiled的Native方法,参数类型是JNIFieldClass的实例,在其本地方法中我们会通过该实例进行对Java实例属性的访问和修改一系列操作。下面让我们对本地代码以一一分析。
获取JNIFieldClass类实例变量mString的值并修改:
在该部分代码中我们会通过JNI系列函数获取Java类实例属性的值并修改,然后将修改前后的值打印出来。
(1) 通过JNIFieldClass类对象object_in获取Class的引用,可能获取到的为NULL,必须加上异常判断。
(2) 调用函数GetFieldID获取JNIFieldClass类的实例变量mString的属性ID,其中第二参数是变量的名称,第三个参数是变量类型的描述符。
(3) 调用GetObjectField获取JNIFieldClass类实例变量mString的值,并打印出来。
(4) 调用SetObjectField修改类实例变量mStriing的值。
(5) 调用DeleteLocalRef释放局部引用。

获取JNIFieldClass类实例int型变量mInt的值并修改:
(1) 调用GetFieldID获取JNIFieldClass类实例int型变量mString的属性ID。
(2) 调用GetIntField获取JNIFieldClass实例变量mInt的值。
(3) 调用SetIntField修改JNIFieldClass实例变量mInt的值。
(4) 最后调用DeleteLocalRef删除局部引用,即对JNIFieldClass的类引用


3.2 JNI访问和修改Java类对象静态属性

在前面的章节带领读者见证了JNI访问和修改Java实例属性,那么接下来让我带领读者来看看怎么通过JNI访问和修改Java类对象静态属性。
Java端代码:
Java实例所属类JNIFieldClass.java代码:

package com.pax.jni.field;

public class JNIFieldClass {
    private static String mStaticString = "Hello JNI, this is static string !";
    private static int mStaticInt = 0;


    public static String toStaticString() {
        return "JNIFieldClass [mStaticString=" + mStaticString + ", mStaticInt=" + mStaticInt + "]";
    }
}

Java端Native方法定义JNIAccessFieldManager.java代码:

package com.pax.jni.field;

/***
 * 
 * C/C++访问Java类的实例变量和静态变量
 * 
 */
public class JNIAccessFieldManager {

    public native void accessStaticField();
    static {
        System.loadLibrary("accesfield");// 加载so库
    }
}

Java端测试代码:

    private void operateStaticFiled(){
        Log.e("ACCESS_FIELD","operateStaticFiled before : " + JNIFieldClass.toStaticString());
        JNIAccessFieldManager mAccessFieldManager = new JNIAccessFieldManager();
        mAccessFieldManager.accessStaticField();
        Log.e("ACCESS_FIELD","operateStaticFiled after : " + JNIFieldClass.toStaticString());
    }

JNI端代码:
Java中Native方法对应com_pax_jni_field_JNIAccessFieldManager.h代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_pax_jni_field_JNIAccessFieldManager */

#ifndef _Included_com_pax_jni_field_JNIAccessFieldManager
#define _Included_com_pax_jni_field_JNIAccessFieldManager
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_pax_jni_field_JNIAccessFieldManager
 * Method:    accessStaticField
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_pax_jni_field_JNIAccessFieldManager_accessStaticField
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_pax_jni_field_JNIAccessFieldManager.cpp代码如下:

#include "com_pax_jni_field_JNIAccessFieldManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <errno.h>

#define TAG "ACCESS_FIELD"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)

//将jstring转换成char *
char* jstringToNative(JNIEnv *env, jstring jstr)
{
	if ((env)->ExceptionCheck() == JNI_TRUE || jstr == NULL)
	{
		(env)->ExceptionDescribe();
		(env)->ExceptionClear();
		//printf("jstringToNative函数转换时,传入的参数str为空");
		return NULL;
	} 
 
	jbyteArray bytes = 0; 
	jthrowable exc; 
	char *result = 0; 
	if ((env)->EnsureLocalCapacity(2) < 0) 
	{ 
		return 0; /* out of memory error */ 
	} 
	jclass jcls_str = (env)->FindClass("java/lang/String"); 
	jmethodID MID_String_getBytes = (env)->GetMethodID(jcls_str, "getBytes", "()[B"); 
 
	bytes = (jbyteArray)(env)->CallObjectMethod(jstr, MID_String_getBytes); 
	exc = (env)->ExceptionOccurred(); 
	if (!exc) 
	{ 
		jint len = (env)->GetArrayLength( bytes); 
		result = (char *)malloc(len + 1); 
		if (result == 0) 
		{ 
			//JNU_ThrowByName( "java/lang/OutOfMemoryError", 	0); 
			(env)->DeleteLocalRef(bytes); 
			return 0; 
		} 
		(env)->GetByteArrayRegion(bytes, 0, len, (jbyte *)result); 
		result[len] = 0; /* NULL-terminate */ 
	} 
	else 
	{ 
		(env)->DeleteLocalRef( exc); 
	} 
	(env)->DeleteLocalRef( bytes); 
	return (char*)result; 

} 

//将char *  转换成 jstring
jstring nativeTojstring( JNIEnv* env,const char* str )
{	
	//定义java String类 strClass
	jclass strClass = (env)->FindClass("java/lang/String");
	//获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
	jmethodID ctorID = (env)->GetMethodID( strClass, "<init>", "([BLjava/lang/String;)V");
	//建立byte数组
	jbyteArray bytes = (env)->NewByteArray( (jsize)strlen(str));
	//将char* 转换为byte数组
	(env)->SetByteArrayRegion( bytes, 0, (jsize)strlen(str), (jbyte*)str);
	//设置String, 保存语言类型,用于byte数组转换至String时的参数
	jstring encoding = (env)->NewStringUTF( "utf-8"); 
	//将byte数组转换为java String,并输出
	return (jstring)(env)->NewObject( strClass, ctorID, bytes, encoding); 
}

/*
 * Class:     com_pax_jni_field_JNIAccessFieldManager
 * Method:    accessStaticField
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_pax_jni_field_JNIAccessFieldManager_accessStaticField
  (JNIEnv * env, jobject object)
{
	jclass clazz;//JNIFieldClass类引用
	jfieldID mStaticString_fieldID;//JNIFieldClass类静态变量mStaticString属性ID
	jfieldID mStaticInt_filedID;//JNIFieldClass类静态变量StaticInt属性ID
	
	/******获取JNIFieldClass类静态变量mStaticString的值并修改********/
	//1.通过FindClass获取JNIFieldClass的类引用
	clazz = env->FindClass("com/pax/jni/field/JNIFieldClass");
	if(clazz == NULL)
	{
		LOGE(TAG,"FindClass failed\n");
		return;
	}

	//2.获取JNIFieldClass类静态变量mStaticString的属性ID
	mStaticString_fieldID = env->GetStaticFieldID(clazz, "mStaticString", "Ljava/lang/String;");	
	if(mStaticString_fieldID == NULL)
	{
		LOGE(TAG,"mStaticString_fieldID  mStaticString failed\n");
		return;		
	}

	//3.获取JNIFieldClass类静态变量mStaticString的值并打印
	jstring j_string = (jstring)env->GetStaticObjectField(clazz, mStaticString_fieldID);
	char * buf = jstringToNative(env, j_string);
	LOGE(TAG,"JNIFieldClass.mString : %s\n", buf);
	free(buf);

	//4.修改JNIFieldClass类静态变量mStaticString的值
	char * buf_out = "Hello Java, I am JNI!";
	env->SetStaticObjectField(clazz, mStaticString_fieldID, nativeTojstring(env, buf_out));
	

	//5.释放局部引用
	env->DeleteLocalRef(j_string);



	/******获取JNIFieldClass类静态变量mStaticInt的值并修改********/
	//6.获取JNIFieldClass类静态变量mStaticInt的属性ID
	mStaticInt_filedID = env->GetStaticFieldID(clazz, "mStaticInt", "I");
	if(mStaticInt_filedID == NULL)
	{
		LOGE(TAG,"GetStaticFieldID  mStaticInt failed\n");
		return;		
	}

	//7.获取JNIFieldClass类静态变量mStaticInt的值并打印
	jint mInt = env->GetStaticIntField(clazz, mStaticInt_filedID);
	LOGE(TAG,"JNIFieldClass.mInt : %d\n", mInt);


	//8.修改JNIFieldClass类静态变量mStaticInt的值
	env->SetStaticIntField(clazz, mStaticInt_filedID, 1000);

	//9.删除局部引用,即对JNIFieldClass的类引用
	env->DeleteLocalRef(clazz);	
	
}

运行演示:

λ aadb logcat  -s  ACCESS_FIELD
--------- beginning of main
--------- beginning of system
12-26 09:27:58.422  8733  8733 E ACCESS_FIELD: operateStaticFiled before : JNIFieldClass [mStaticString=Hello JNI, this is static string !, mStaticInt=0]
12-26 09:27:58.426  8733  8733 I ACCESS_FIELD: JNIFieldClass.mString : Hello JNI, this is static string !
12-26 09:27:58.426  8733  8733 I ACCESS_FIELD: JNIFieldClass.mInt : 0
12-26 09:27:58.426  8733  8733 E ACCESS_FIELD: operateStaticFiled after : JNIFieldClass [mStaticString=Hello Java, I am JNI!, mStaticInt=1000]

案例分析:
在本例中,Java类JNIAccessFieldManager中定义了一个accessStaticField的Native方法,可以看到我们这里是没有传入任何参数的,因为我们是对类对象做访问不需要它的实例。下面让我们对本地代码以一一分析。
获取JNIFieldClass类静态变量mStaticString的值并修改:
在该部分代码中我们会通过JNI系列函数获取Java类对象静态属性的值并修改,然后将修改前后的值打印出来。
(1) 通过FindClass获取JNIFieldClass的类引用,可能获取到的为NULL,必须加上异常判断。
(2) 调用函数GetStaticFieldID获取JNIFieldClass类静态变量mStaticString的属性ID。
(3) 调用GetStaticObjectField获取JNIFieldClass类静态变量mStaticString的值并打印。
(4) 调用SetStaticObjectField修改JNIFieldClass类静态变量mStaticString的值。
(5) 调用DeleteLocalRef释放局部引用。

获取JNIFieldClass类静态变量mStaticInt的值并修改:
(1) 调用GetStaticFieldID获取JNIFieldClass类静态变量mStaticInt的属性ID。
(2) 调用GetStaticIntField获取JNIFieldClass类静态变量mStaticInt的值并打印。
(3) 调用SetStaticIntField修改JNIFieldClass类静态变量mStaticInt的值。
(4) 最后调用DeleteLocalRef删除局部引用,即对JNIFieldClass的类引用
好了到这里就解析完成了。



四. 总结思考

大家对前面的实例有没有一个疑问呢,为啥我们通过JNI能访问Java类实例私有属性和Java类对象私有静态属性呢。这是为什么呢?

public class JNIFieldClass {
    private static String mStaticString = "Hello JNI, this is static string !";//私有
    private String mString = "Hello JNI, this is normal string !";//私有
    private static int mStaticInt = 0;//私有
    private int mInt = 1;//私有
}

由于 JNI 函数是直接操作虚拟机中的数据结构,不受 Java 访问修饰符的限制。即,在本地代码中可以调用JNI 函数可以访问 Java 对象中的非 public 属性和方法。这也为操作Java对象中private的属性或者方法的一种思路。

前面的知识讲解都是为了引出最后的结论,下面让我们小结一把:
JNI访问和修改Java实例属性操作步聚:

  • 调用 GetObjectClass 函数获取实例对象的 Class 引用
  • 调用 GetFieldID 函数获取 Class 引用中某个实例变量的 ID
  • 调用 GetXXXField 函数获取变量的值,需要传入实例变量所属对象和变量 ID
  • 调用 SetXXXField 函数修改变量的值,需要传入实例变量所属对象、变量 ID 和变量的值
    JNI访问和修改Java类对象静态属性操作步聚:
  • 调用 FindClass 函数获取类的 Class 引用
  • 调用 GetStaticFieldID 函数获取 Class 引用中某个静态变量 ID
  • 调用 GetStaticXXXField 函数获取静态变量的值,需要传入变量所属 Class 的引用和变量 ID
  • 调用 SetStaticXXXField 函数设置静态变量的值,需要传入变量所属 Class 的引用、变量 ID和变量的


写在最后

  各位读者看官朋友们,关于C/C++通过JNI访问Java实例属性和类静态属性就告一段落了。本篇几乎将JNI中访问Java实例属性和类静态属性各种情况都讲到了,只要仔细阅读本章,应该以后没有访问Java实例属性和类静态属性问题能难住各位了。在最后麻烦读者朋友们如果本篇对你有帮助,关注和点赞一下,当然如果有错误和不足的地方也可以拍砖。

发布了89 篇原创文章 · 获赞 92 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/tkwxty/article/details/103703341
今日推荐