JNI开发,Java调用C++ , C++ 调用Java,Java-Native 相互调用 , Native反射调用java类、方法、属性等

这篇主要写一下JNI的Native开发, 没看JNI基础知识的可以去上一篇文章看一下。
JNI初识,函数规范,基本数据类型,与之对应签名

这里我们要在Android Studio中进行JNI的开发,首先创建一个NDK工程,然后打开cpp目录下的  native-lib.cpp  进行jni的开发。其中有一些c与c++的语法,如果又看不懂的可以看我的几篇别的文章了解一下c与c++语法。

首先说一下jni基本数据类型的使用

#include "jni.h"
#include <iostream>

//c++中需要以c的方式编译
extern "C"
//JNIEnv: 由Jvm传入与线程相关的变量。定义了JNI系统操作、java交互等方法。
//jobject: 表示当前调用对象,即 this , 如果是静态的native方法,则获得jclass
JNIEXPORT jstring JNICALL Java_com_dongnao_jniTest_ExampleUnitTest_test
(JNIEnv *env, jobject, jint i, jstring j, jfloat k) {
	// 获得c字符串  
	// 参数1 需要获取的字符串
	// 参数2 是否拷贝
	//提供一个boolean(int)指针,用于接收jvm传给我们的字符串是否是拷贝的。
	//通常,我们不关心这个,一般传个NULL就可以
	const char* str = env->GetStringUTFChars(j, JNI_FALSE);

	char returnStr[100];
	//格式化字符串
	sprintf_s(returnStr, "C++ string:%d,%s,%f\n", i, str, k);
	
	//释放掉内存 x
	env->ReleaseStringUTFChars(j,str);
	// 返回java字符串
	return  env->NewStringUTF(returnStr);
}

在java 中的定义与调用该Native方法:

//调用Nitive 方法
String s = test(1,"java", 1.1f);

//写一个方法,但是不实现,直接就按这种方式定义,以供与Nativie方法链接
native String test(int i,String  j,float k);

输出:

这个Native方法中我们可以看出,除了jstring类型的数据外,其他我们可以直接使用。而jstring类型的数据我们需要使用 env 变量,然后通过 GetStringUTFChars 获取char 指针,而且使用完之后需要 ReleaseStringUTFChars 进行手动释放。‘运行之后输出了相应的结果。

 

接下来我们看一下c/c++中获取java数组的写法:

java 中的定义和使用 :


int i[] = {11,22,33,44};
String j[] = {"测","试"};
testarr(i,j);

native int testarr(int[] i, String[] s);

Native方法:

extern "C"
JNIEXPORT jint JNICALL Java_com_example_costomviewtext_ExampleUnitTest_testarr
(JNIEnv *env, jobject instance,jintArray i_,jobjectArray s_)
{	
	//指向数组首元素地址
	//第二个参数:指针;指向内存地址
	//在这个地址存数据
	//true:是拷贝的一个新数据(新申请内存)
	//false:就是使用的java的数组(地址)
	jint *i  = env->GetIntArrayElements(i_, NULL);

	int32_t  length = env->GetArrayLength(i_);

	for (int  k = 0; k < length; ++k)
	{
		printf("java---jint%d\n",*(i+k));
	}
	env->ReleaseIntArrayElements(i_,i,0);

	jint strlength = env->GetArrayLength(s_);
	for (int k = 0; k < strlength; ++k)
	{	
		jstring str = static_cast<jstring>(env->GetObjectArrayElement(s_, k));//根据下标获取数组中的
		//转成可操作的字符串
		const char* s = env->GetStringUTFChars(str, 0);
		printf("java----jstring %s\n",s);
		env->ReleaseStringUTFChars(str, s);
	}
	return 100;
}

输出两个数组中的值:

这个方法接收了两个java数组,我们这里针对有差异性的两种数组进行针对性处理。
首先试 jint 类型的数组,在这里我们使用 GetIntArrayElements 获取数组指针,接着使用 GetArrayLength 获取数组长度,之后使用循环数组指针,最后使用数组指针记得释放。

然后我们处理 jobject 类型的数组,这里我们也是使用 GetArrayLength 获取数组长度,之后循环获取,然后使用c++中的类型转换转换到 jstring 类型,最后在使用上面同样的获取jstring类型的方法,就可以使用了,同样不能忘记释放内存。

 

最后看看Native中c/c++反射java,反射调用方法,反射修改属性 :

java代码:

 //java中的类 :
public class Bean {

    private static final String TAG = "Bean";

    private int i = 100;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        System.out.println("setI success :"+i);
        this.i = i;
    }

    public static void printInfo(String msg){
        System.out.println(msg);
    }

}

//java中的定义与调用:
native void paseObject(Bean bean, String str);

Bean bean = new Bean();
paseObject(bean);

Native方法:

extern "C"
JNIEXPORT void JNICALL Java_com_example_costomviewtext_ExampleUnitTest_paseObject
(JNIEnv *env,jobject instance,jobject bean)
{
	//反射调用java的方法
	//1.获取java对应的class对象
	jclass beanCls = env->GetObjectClass(bean);
	//2.找到要调用的方法
	jmethodID getI  =  env->GetMethodID(beanCls, "getI", "()I"); // 签名
	//3.调用
	jint i = env->CallIntMethod(bean, getI);
	cout << i << endl;

	//set方法
	jmethodID setI = env->GetMethodID(beanCls, "setI","(I)V");
	env->CallVoidMethod(bean, setI, 200);

	//static 方法
	jmethodID printInfo = env->GetStaticMethodID(beanCls, "printInfo", "(Ljava/lang/String;)V");
	//创建java字符串
	jstring str2 = env->NewStringUTF("printInfo is called ");
	env->CallVoidMethod(beanCls,printInfo, str2);
	//释放局部引用
	env->DeleteLocalRef(str2);

	//修改属性值
	jfieldID fileId = env->GetFieldID(beanCls,"i","I");//获取属性
	env->SetIntField(bean,fileId,300);
	jint amendi = env->CallIntMethod(bean, getI);
	cout << "修改后的i:" << amendi << endl;

	env->DeleteLocalRef(beanCls);

}

输出:

在这里我们通过 GetObjectClass 反射获取 jclass 对象, 然后使用 GetMethodID 找到要调用的方法,这里就用到了上篇
文章中说的签名了。
GetMethodID 这个方法第一个参数需要传 jclass 对象,第二个参数需要传我们要找的方法名,第三个参数需要传方法的
签名,可以看到上面我们的Bean类中的 getI 方法不需要传参数,返回值是int ,所以这里对应的就是 ()I 括号内的是穿的参数,
后面带着返回值,int对应的是I,所以这里就传 “()I”。
然后接着  CallVoidMethod 调用就可以了。
后面的 setI 的调用流程和 getI 一样了,不同的是,setI 的需要传一个int值,返回值是void,所以这里它的签名就是"(I)V"。
在  CallVoidMethod 调用 setI 的时候还得将参数传过去。

还有一个static方法的示例,与普通不同的是 这里要使用的方法是 GetStaticMethodID ,参数都一样,上面我举例的这个
static方法的参数是一个string值,所以这里他的签名就是 "(Ljava/lang/String;)V" 这样。

修改属性的就使用 GetFieldID 获取,SetIntField 修改。

说了这么多,其实具体说来不怎么难,都是一些api的调用,不涉及一些逻辑算法这些,大家看过之后练习几遍自然就掌握了,这些东西在进行Ndk开发,比如我们自己编译了ffmpeg的库,然后在调用其中的一些方法,与之链接的话就需要使用JNI进行开发,JNI在其中扮演的角色就是中间链接,java 调用 Native 方法 使用native 定义之后使用,而Native调用java的时候就需要用到反射了,这部分比较重要,稍微麻烦点。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值