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

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

  在前面的章节JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性中讲解了C/C++通过JNI对Java实例属性和静态属性的访问。今天我们继续向JNI的知识海洋进军讲解C/C++通过JNI访问Java通过JNI访问Java实例方法和类静态方法的处理。本章内容有点多哦,所以读者务必上好厕所,搬个小板凳认真围观。

前言

通过前面的正佳我们知道了如何通过 JNI 函数来访问Java实例属性和类静态属性。接下来我们接着学习C/C++本地代码如何通过JNI访问Java类实例方法和静态方法。在正式开讲之前,先我们思考一下在 Java 中调用一个方法时在 虚拟机中的实现原理,有助于下面讲解本地代码调用 Java 方法实现的机制。有过 Java开发经验的小伙伴都知道,调用一个类的静态方法,直接通过 类名.方法 就可以调用。看起来很简单,其实不然,在这个调用过程中,虚拟机是帮我们做了很多工作的,主要是类的加载验证。当我们在运行一个 Java 程序时,虚拟机 会先将程序运行时所要用到所有相关的 class 文件加载到 虚拟机中,并采用按需加载的方式加载,也就是说某个类只有在被用到的时候才会被加载,这样设计的目的也是为了提高程序的性能和节约内存。所以我们在用类名调用一个静态方法之前,虚拟机 首先会判断该类是否已经加载,如果没有被 ClassLoader 加载到 虚拟机中,虚拟机 会从classpath 路径下查找该类,如果找到了,会将其加载到 虚拟机中,然后才是调用该类的静态方法。如果没有找到,虚拟机会抛出 java.lang.ClassNotFoundException 异常,提示找不到这个类。ClassLoader 是 虚拟机加载class 字节码文件的一种机制,关于Java ClassLoader可以参阅篇章详细深入分析 Java ClassLoader 工作机制一文。其实在 JNI 开发当中,本地代码也是按照上面的流程来访问类的静态方法或实例方法的,



一. 初探JNI访问Java实例非静态方法处理函数

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

1.1 GetMethodID

函数原型: jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char* name, const char* sig)
函数功能: 返回Java类或者接口实例非静态方法的方法ID(可以参照属性ID),方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。要获得构造函数的方法 ID,应将 作为方法名,同时将 void (V) 作为返回类型,具体使用可以参见后面章节实战讲解。
参数:

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

函数返回值:该Java类实例或者接口实例非静态方法ID,如果找不到指定的方法,则为 NULL。
异常抛出:

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

1.2 CallMethod

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: NativeType CallMethod (JNIEnv*en v, jobject obj , jmethodID methodID, …)
函数功能: 根据所指定的方法 ID 调用 Java 对象的实例(非静态)方法。参数 methodID 必须通过调用 GetMethodID() 来获得。当这些函数用于调用私有方法和构造函数时,方法 ID 必须从obj 的真实类派生而来,而不应从其某个超类派生。当然,附加参数可以为空 。这里的调用Java对象方法的参数都是附加在最后面的,并且参数的顺序必须按照Java类对象方法中参数一一匹配的。
使用说明: 在实际使用中,根据需要调用方法将 CallMethod中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);

参数:

  • env: JNIEnv接口指针
  • obj: Java实例对象,可以是通过JNI从Java传递下来的,也可以是在本地方法中创建的。
  • methodID: 通过GetMethodID获取的方法ID
  • : 方法参数

函数返回值: 返回调用Java方法的结果。
异常抛出: 执行Java方法时抛出的异常信息。

函数集表格:

CallMethod NativeType JNI本地类型
CallVoidMethod( ) V
CallObjectMethod( ) jobject
CallBooleanMethod ( ) jboolean
CallByteMethod( ) jbyte
CallCharMethod( ) jchar
CallShortMethod( ) jshort
CallIntMethod( ) jint
CallLongMethod() jlong
CallFloatMethod() jfloat
CallDoubleMethod() jdouble

1.3 CallMethodA

读者是不是初一看,以为我老糊涂了咋又把前面的章节重写一遍呢,真不是读者仔细看看是不是多了一个A,前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和1.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType CallMethodA (JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args)
使用说明: 在实际使用中,根据需要调用方法将 CallMethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的参数是以指针的形式添加的。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义以及调用:

void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
//jvalue定义如下:
typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

//例如
    jvalue * args=new jvalue[3];
     args[0].i=12L;
     args[1].d=1.2;
     args[2].c=L'c';
     jmethodID xxx =env->GetMethodID(env->GetObjectClass(obj),"xxx","(IDC)V");
     env->CallVoidMethodA(obj,xxx,args);
     delete []args;  //释放内存

特别说明:这个函数集合CallMethod用法一致,就不过多细说了。函数集基本也是一样。


1.4 CallMethodV

读者是不是猛地一看,以为作者凑字数咋又把前面的章节重写一遍呢,真不是此处多了一个V,前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和1.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType CallMethodV (JNIEnv *env, jobject obj, jmethodID methodID, va_list args)
使用说明: 在实际使用中,根据需要调用方法将 CallMethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的va_list args。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);

特别说明:这个函数集合CallMethod用法一致,就不过多细说了,并且在C++环境下最后的CallMethod里面其实是封装的CallMethodV,代码为证:

    void CallVoidMethod(jobject obj, jmethodID methodID, ...)
    {
        va_list args;
        va_start(args, methodID);
        functions->CallVoidMethodV(this, obj, methodID, args);
        va_end(args);
    }


二. 初探JNI访问Java类静态方法处理函数

在前面的章节降解了JNI访问Java实例对象非静态方法的一系列函数,下面让我们来看看JNIEnv为我们提供了那些常见JNI访问Java类静态方法处理函数。

2.1 GetStaticMethodID

函数原型: jmethodID GetStaticMethodID(JNIEnv * env, jclass clazz, const char* name, const char* sig)
函数功能: 返回Java类对象静态方法的方法ID(可以参照属性ID),方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。具体使用可以参见后面章节实战讲解。
参数:

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

函数返回值:该Java类对象静态方法ID,如果找不到指定的方法,则为 NULL。
异常抛出:

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

2.2 CallStaticMethod

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: NativeType CallStaticMethod (JNIEnv*en v, jclass clazz , jmethodID methodID, …)
函数功能: 根据所指定的方法 ID 调用 Java 类对象的静态方法。参数 methodID 必须通过调用 GetStaticMethodID() 来获得。当然,附加参数可以为空 。这里的调用Java对象方法的参数都是附加在最后面的,并且参数的顺序必须按照Java类对象方法中参数一一匹配的。
使用说明: 在实际使用中,根据需要调用方法将 CallStaticMethod中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);

参数:

  • env: JNIEnv接口指针
  • clazz: Java类对象,通过findClass方法得到
  • methodID: 通过GetStaticMethodID获取的方法ID
  • : 方法参数,即该方法在Java中的具体参数入参

函数返回值: 返回调用Java方法的结果。
异常抛出: 执行Java方法时抛出的异常信息。

函数集表格:

CallStaticMethod NativeType JNI本地类型
CallStaticVoidMethod( ) V
CallStaticObjectMethod( ) jobject
CallStaticBooleanMethod ( ) jboolean
CallStaticByteMethod( ) jbyte
CallStaticCharMethod( ) jchar
CallStaticShortMethod( ) jshort
CallStaticIntMethod( ) jint
CallStaticLongMethod() jlong
CallStaticFloatMethod() jfloat
CallStaticDoubleMethod() jdouble

2.3 CallStaticMethodA

读者此时看到这个应该已经有心理准备了,因为我们前面已经见识过了,所以这里我再次申明真不是为了凑字数。前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和2.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType CallStaticMethodA (JNIEnv *env, jclazz clazz, jmethodID methodID, jvalue *args)
使用说明: 在实际使用中,根据需要调用方法将 CallStaticMethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的参数是以指针的形式添加的。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义以及调用:

void        (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, jvalue*);

特别说明:这个函数集合CallStaticMethod用法一致,就不过多细说了。函数集也是一样。


2.4 CallStaticMethodV

又是一个马甲,前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和2.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType CallStaticMethodV (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)
使用说明: 在实际使用中,根据需要调用方法将 CallStaticMethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的va_list args。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);

特别说明:这个函数集合CallMethod用法一致,就不过多细说了,并且在C++环境下最后的CallMethod里面其实是封装的CallMethodV,代码为证:

    void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
    {
        va_list args;
        va_start(args, methodID);
        functions->CallStaticVoidMethodV(this, clazz, methodID, args);
        va_end(args);
    }


三. 访问Java实例方法和类静态方法实战分析

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

3.1 JNI访问Java实例非静态方法

好了前面的理论知识我们已经OK了,那么接下来都懂的必须来点实战的东西,不能光说不练。让我带领读者来看看怎么通过JNI访问Java实例方法,这里会提供两种方法,其差别主要是Java实例是通过JNI传递到本地方法中还是在本地方法中创建。
Java端代码:
Java实例所属类JNIMethodClass.java代码:

package com.xxx.jni.method;
import android.util.Log;

public class JNIMethodClass {
    private int jniCallInstanceMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callInstanceMethod");
        Log.e("ACCESS_METHOD", "callInstanceMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
        return 0;
    }
}

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

package com.xxx.jni.method;
public class JNIAccessMethodManager {
    public  native void callInstanceMethod();//不传递Java对象,在C++层创建一个Java对象并调用其方法
    
    static{
        System.loadLibrary("accessmethod");
    }
}

Java端测试代码:

    private void operateInstanceMethod(){
        JNIAccessMethodManager mjniMethodManager = new JNIAccessMethodManager();
        mjniMethodManager.callInstanceMethod();
    }

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

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_xxx_jni_method_JNIAccessMethodManager */

#ifndef _Included_com_xxx_jni_method_JNIAccessMethodManager
#define _Included_com_xxx_jni_method_JNIAccessMethodManager
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xxx_jni_method_JNIAccessMethodManager
 * Method:    callInstanceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callInstanceMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_xxx_jni_field_JNIAccessFieldManager.cpp代码如下:

#include"com_xxx_jni_method_JNIAccessMethodManager.h"

#include 
#include 
#include 
#include 
#include 


#define TAG "ACCESS_METHOD"
#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, "", "([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_xxx_jni_method_JNIAccessMethodManager
 * Method:    callInstanceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callInstanceMethod
  (JNIEnv * env, jobject object)
{
	/*******JNI层创建Java实例对象并调用非静态方法*****/
	//1.从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用
	jclass clazz = env->FindClass("com/xxx/jni/method/JNIMethodClass");
	if(NULL == clazz)
	{
		LOGE(TAG, "FindClass failed\n");
		return;
	}

	//2.获取类JNIMethodClass的默认构造函数ID,通常都是这么写
	jmethodID clazz_construct_method = env->GetMethodID(clazz, "","()V");
	if(NULL == clazz_construct_method)
	{
		LOGE(TAG,"GetMethodID for construct failed\n");
	}

	//3.在JNI层创建JNIMethodClass类的实例
	jobject object_out = env->NewObject(clazz, clazz_construct_method);
	if(NULL == object_out)
	{
		LOGE(TAG,"NewObject failed\n");
	}


	//4.查找JNIMethodClass实例对象的非静态方法jniCallInstanceMethod
	//其中第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名
	jmethodID jniCallInstanceMethod_method = env->GetMethodID(clazz, "jniCallInstanceMethod", "(Ljava/lang/String;IBCZJDFS)I");
	if(NULL == jniCallInstanceMethod_method)
	{
		LOGE(TAG,"GetMethodID failed\n");
		return;
	}

	//5.调用Java对象的实例方法jniCallInstanceMethod
	jstring string_out = nativeTojstring(env, "Hello jniCallInstanceMethod!");
	jboolean    z = 1;
    jbyte       b = 2;
    jchar       c = 'c';
    jshort      s = 60;
    jint        i = 100;
    jlong       j = 568978523;
    jfloat      f = 125643.22F;
    jdouble     d = 123456.12D;
	env->CallIntMethod(object_out, jniCallInstanceMethod_method, string_out, i, b, c, z, j, d, f, s);


	//6.最后删除局部引用
	env->DeleteLocalRef(object_out);
	env->DeleteLocalRef(clazz);	
}

运行演示:

λ adb logcat  -s  ACCESS_METHOD
--------- beginning of main
--------- beginning of system
E/ACCESS_METHOD(10421): callInstanceMethod
E/ACCESS_METHOD(10421): callInstanceMethod [str=Hello jniCallInstanceMethod!, i=100, b=2, c=c, bl=true, l=568978523, d=123456.12, f=125643.22, sh=60]

案例分析:
在本例中,Java类JNIAccessMethodManager中定义了一个callInstanceMethod的Native方法,参数类型为void,在其本地方法中我们会通过JNI来访问Java类实例的非静态方法。下面让我们对本地代码以一一分析。
JNI层创建Java实例对象并调用非静态方法:
(1) 调用FindClass从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用。此时可能获取到的类的引用为NULL,所以要做异常的判断处理。
(2) 调用GetMethodID获取类JNIMethodClass的默认构造函数ID,注意这里默认构造函数名和签名。并且这里也要做异常处理。
(3) 调用NewObject在JNI层创建JNIMethodClass类的实例。
(4) 调用GetMethodID 查找JNIMethodClass实例对象的非静态方法jniCallInstanceMethod,其中第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名。
(5) 调用CallIntMethod来访问Java对象的实例方法jniCallInstanceMethod,这里重点关注一下参数的传递。
(6) 最后调用DeleteLocalRef删除局部引用


3.2 JNI访问Java类对象静态方法

在前面的章节里面我们介绍了JNI访问Java对象实例非静态方法,在本节中将要介绍JNI访问Java类对象静态方法。
Java端代码:
Java实例所属类JNIMethodClass.java代码:

package com.xxx.jni.method;
import android.util.Log;

public class JNIMethodClass {
    private static void jniCallStaticMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callStaticMethod");
        Log.e("ACCESS_METHOD", "callStaticMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
    }
}

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

package com.xxx.jni.method;
public class JNIAccessMethodManager {
    public native void callStaticMethod();
    
    static{
        System.loadLibrary("accessmethod");//Java类对象静态方法
    }
}

Java端测试代码:

    private void operateStaticMethod(){
        JNIAccessMethodManager mjniMethodManager = new JNIAccessMethodManager();
        mjniMethodManager.callStaticMethod();
    }

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

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_xxx_jni_method_JNIAccessMethodManager */

#ifndef _Included_com_xxx_jni_method_JNIAccessMethodManager
#define _Included_com_xxx_jni_method_JNIAccessMethodManager
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xxx_jni_method_JNIAccessMethodManager
 * Method:    callStaticMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callStaticMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_xxx_jni_field_JNIAccessFieldManager.cpp代码如下:

#include"com_xxx_jni_method_JNIAccessMethodManager.h"

#include 
#include 
#include 
#include 
#include 


#define TAG "ACCESS_METHOD"
#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, "", "([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_xxx_jni_method_JNIAccessMethodManager
 * Method:    callStaticMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callStaticMethod
  (JNIEnv * env, jobject object)
{
	/*******JNI调用Java类对象静态方法*****/
	jclass clazz = NULL;
	//1.从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用
	clazz = env->FindClass("com/xxx/jni/method/JNIMethodClass");
	if(NULL == clazz){
		LOGE(TAG,"FindClass failed\n");
		return;
	}

	//2.从clazz类对象中查找静态方法jniCallStaticMethod的ID
	//第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名
	jmethodID jniCallStaticMethod_method = env->GetStaticMethodID(clazz, "jniCallStaticMethod", "(Ljava/lang/String;IBCZJDFS)V");
	if(NULL == jniCallStaticMethod_method)
	{
		LOGE(TAG,"GetMethodID failed\n");
		return;
	}

	//3.调用clazz类对象的jniCallStaticMethod静态方法
	//第一个参数为JNIMethodClass的class引用,第二个参数未jniCallStaticMethod的methodID,第三个参数为...不定长,根据实际情况来确定
	jstring string_out = nativeTojstring(env, "Hello jniCallStaticMethod!");
	jboolean    z = 1;
    jbyte       b = 2;
    jchar       c = 'c';
    jshort      s = 60;
    jint        i = 100;
    jlong       j = 568978523;
    jfloat      f = 125643.22F;
    jdouble     d = 123456.12D;
	env->CallStaticVoidMethod(clazz, jniCallStaticMethod_method, string_out, i, b, c, z, j, d, f, s);


	//4.删除局部引用
	env->DeleteLocalRef(string_out);
	env->DeleteLocalRef(clazz);
}

运行演示:

λ adb logcat  -s ACCESS_METHOD
--------- beginning of main
--------- beginning of system
E/ACCESS_METHOD( 6396): callStaticMethod
E/ACCESS_METHOD( 6396): callStaticMethod [str=Hello jniCallStaticMethod!, i=1, b=255, c=c, bl=true, l=4294967296010, d=100.0, f=0.0, sh=2130903040]

案例分析:
在本例中,Java类JNIAccessMethodManager中定义了一个callStaticMethod的Native方法,参数类型为void,在其本地方法中我们会通过JNI来访问Java类对象的静态方法。下面让我们对本地代码以一一分析。
JNI层回调Java类对象并调用静态方法:
(1) 调用FindClass从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用。此时可能获取到的类的引用为NULL,所以要做异常的判断处理。
(2) 调用GetStaticMethodID从clazz类对象中查找静态方法jniCallStaticMethod的ID,这里的第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名。并且这里也要做异常处理。
(3) 使用CallStaticVoidMethod函数调用调用clazz类对象的jniCallStaticMethod静态方法,其中第一个参数为JNIMethodClass的class引用,第二个参数未jniCallStaticMethod的methodID,第三个参数为…不定长,根据实际情况来确定。
(4) 调用GetMethodID 查找JNIMethodClass实例对象的非静态方法jniCallInstanceMethod,其中第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名。
(5) 最后调用DeleteLocalRef删除局部引用



四. 总结思考

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

public class JNIMethodClass {
    private static void jniCallStaticMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callStaticMethod");
        Log.e("ACCESS_METHOD", "callStaticMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
    }
    private int jniCallInstanceMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callInstanceMethod");
        Log.e("ACCESS_METHOD", "callInstanceMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
        return 0;
    }
}

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

前面的知识讲解都是为了引出最后的结论,下面让我们小结一把:
JNI创建Java实例并访问该Java实例方法的操作步聚:

  • 调用 FindClass 函数获取类的 Class 引用
  • 调用GetMethodID获取Class类的默认构造函数
  • 调用NewObject创建该Java对象实例
  • 调用GetMethodID获取该Java对象需要访问的实例方法ID
  • 最后调用CallXXXMethod函数访问Javad实例对象方法
  • 最后调用DeleteLocalRef释放前面创建的局部引用

JNI访问Java类对象的静态方法的操作步聚:

  • 调用 FindClass 函数获取类的 Class 引用
  • 调用函数GetStaticMethodID获取将要访问的该Java类对象的静态方法ID
  • 然后使用CallStaticXXXMethod函数来访问Java类对象的静态方法
  • 最后释放创建的局部引用


写在最后

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

你可能感兴趣的:(JNI/NDK入门指南)