一.概述
上一篇博文讲解了如何搭建一个可以加载和链接第三方库、编译C/C++文件的Jni Demo App。
这篇博文在这个Jni Demo App的基础上,从实战出发详细讲解 Jni 开发语法。
接下来,先用一小节将Jni开发比较重要的理论知识点过一下,然后进行代码实战演练。
二.理论
2.1 JavaVM 和 JNIEnv
JavaVM 和 JNIEnv 是定义在 jni.h 头文件中最关键的两个结构体:
- JavaVM: 代表 Java 虚拟机,每个 Java进程有且仅有一个全局的 JavaVM 对象,JavaVM 可以跨线程共享;
- JNIEnv: 代表 Java运行环境,每个 Java线程都有各自独立的 JNIEnv 对象,JNIEnv 不可以跨线程共享。
JavaVM 和 JNIEnv 的类型定义在 C 和 C++ 中略有不同,但本质上是相同的,内部由一系列指向虚拟机内部的函数指针组成。
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
结构体详细定义可翻阅 Jni.h 头文件,在此不作代码列举
2.2.Jni 基础数据类型:
Jni 的数据类型都在 jni.h 头文件中定义,包括基础数据类型(int 等)和引用数据类型(Object、Class、数组等)
(1).C/C++基础数据类型:
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
/* "cardinal indices and sizes" */
typedef jint jsize;
(2).C++引用数据类型:
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
(3).C引用数据类型:
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
(4).与Java数据类型映射表:
Java 类型 | JNI 类型 | 描述 | 长度(字节) |
---|---|---|---|
boolean | jboolean | unsigned char | 1 |
byte | jbyte | signed char | 1 |
char | jchar | unsigned short | 2 |
short | jshort | signed short | 2 |
int | jint、jsize | signed int | 4 |
long | jlong | signed long | 8 |
float | jfloat | signed float | 4 |
double | jdouble | signed double | 8 |
Class | jclass | Class 类对象 | 1 |
String | jstrting | 字符串对象 | / |
Object | jobject | 对象 | / |
Throwable | jthrowable | 异常对象 | / |
boolean[] | jbooleanArray | 布尔数组 | / |
byte[] | jbyteArray | byte 数组 | / |
char[] | jcharArray | char 数组 | / |
short[] | jshortArray | short 数组 | / |
int[] | jinitArray | int 数组 | / |
long[] | jlongArray | long 数组 | / |
float[] | jfloatArray | float 数组 | / |
double[] | jdoubleArray | double 数组 | / |
2.3 JNI 访问 Java 字段 (成员变量)
Jni 访问 Java 字段的流程分为 2 步:
- 1.通过 jclass 获取字段 ID。例:
Fid = env->GetFieldId(clz, "name", "Ljava/lang/String;");
- 2.通过字段 ID 访问字段。例:
Jstr = env->GetObjectField(thiz, Fid);
Java 字段分为静态字段和非静态字段,相关方法如下:
- GetFieldId:获取非静态字段 ID
- GetStaticFieldId:获取静态字段 ID
- GetField:获取类型为 Type 的非静态字段(例如 GetIntField)
- SetField:设置类型为 Type 的非静态字段(例如 SetIntField)
- GetStaticField:获取类型为 Type 的静态字段(例如 GetStaticIntField)
- SetStaticField:设置类型为 Type 的静态字段(例如 SetStaticIntField)
2.4 Jni 调用 Java 方法
Jni 访问 Java 方法与访问 Java 字段类似,访问流程分为 2 步:
- 1、通过 jclass 获取「方法 ID」。例:
Mid = env->GetMethodID(jclass, "helloJava", "()V");
- 2、通过方法 ID 调用方法。例:
env->CallVoidMethod(thiz, Mid);
Java 方法分为静态方法和非静态方法,相关方法如下:
- GetMethodId:获取非静态方法 ID
- GetStaticMethodId:获取静态方法 ID
- CallMethod:调用返回类型为 Type 的非静态方法(例如 GetVoidMethod)
- CallStaticMethod:调用返回类型为 Type 的静态方法(例如 CallStaticVoidMethod)
- CallNonvirtualMethod:调用返回类型为 Type 的父类方法(例如 CallNonvirtualVoidMethod)
2.5 描述符:
Jni在调用Java 字段(成员变量)或函数时,需要用描述符对变量、函数参数 、函数返回值的类型进行签名描述。
字段描述符:描述字段(成员变量)的类型。
JVM 对每种基础数据类型定义了固定的描述符,而引用类型则是以 L 开头的形式:
Java 类型 | 描述符 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
floag | F |
double | D |
void | V |
引用类型 | 以 L 开头 ; 结尾,中间是 / 分隔的包名和类名。例如 String 的字段描述符为 Ljava/lang/String; |
方法描述符: 描述方法的返回值类型和参数表类型
参数类型用一对圆括号括起来,按照参数声明顺序列举参数类型,返回值出现在括号后面。
例如方法 void fun() 的名称为 fun,方法描述符为 ()V
三.实战
3.1 Java调用Jni
(1).Java从Jni 获取一个String
这也是AndroidStudio默认创建的Native C++ Demo里的
jnidemo.cpp
Jni函数名前缀要与Java声明Native函数所在文件的包名+文件名对应
extern "C" {
JNIEXPORT jstring JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaGetStringFromJNI(JNIEnv *env, jobject instance) {
string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
}
JNIDEMO.java
public class JNIDEMO {
//Java从Jni获取String
public native String JavaGetStringFromJNI();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
String jniStr = mJniDemo.JavaGetStringFromJNI();
Log.v(TAG, "jniStr:" + jniStr);
}
}
日志打印:
(2).Java传一个Int[]到Jni,Jni直接处理Int[]数据
jnidemo.cpp
extern "C" {
/***** java传递一个Int[]到Jni, Jni赋值后再返回给Java *****/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaJniTransIntArray(JNIEnv *env, jobject instance,
jintArray javaArr) {
//获取Java数组长度
int lenght = env->GetArrayLength(javaArr);
//GetIntArrayElements() 函数作用就是将 jintArray 转为 int* 指针; 将本地指针指向含有Java端数组的内存地址
//依赖Jvm的具体实现,可能是锁住Java端的那段数组不被回收(增加引用计数),也可能所Jvm在堆上对该数组的一份拷贝,速度和效率比GetIntArrayRegion方法要高很多
int *arrp = env->GetIntArrayElements(javaArr, 0);
//对数组元素进行处理
for (int i = 0; i < lenght; i++) {
*(arrp + i) += i;
}
//将C数组种的元素拷贝到Java数组中
env->SetIntArrayRegion(javaArr, 0, lenght, arrp);
//如果需要,可以返回数组
//return javaArr;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java传一个Int[]到Jni进行数据处理
public native void JavaJniTransIntArray(int[] arrInt);
}
JniActivity.java
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Java传一个int[]到Jni,Jni对int[]元素进行修改
int[] arrInt = new int[10];
Log.v(TAG, "BeforCallJni arrInt[]:" + Arrays.toString(arrInt));
mJniDemo.JavaJniTransIntArray(arrInt);
Log.v(TAG, "AfterCallJni arrInt[]:" + Arrays.toString(arrInt));
}
}
日志打印:
可以看到在调用Jni函数之前,数组元素都是初始值0,经过Jni处理之后数值就改变了
(3).Java传一个byte[]到Jni,Jni直接处理byte[]数据
jnidemo.cpp
extern "C" {
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaJniTransByteArray1(JNIEnv *env, jobject instance,
jbyteArray javaArr) {
//获取Java数组长度
int lenght = env->GetArrayLength(javaArr);
//将 jbyteArray 转为 int* 指针,使用本地指针指向含有Java端数组的内存地址
//依赖Jvm的具体实现,可能是锁住Java端的那段数组不被回收(增加引用计数),也可能所Jvm在堆上对该数组的一份拷贝,速度和效率比GetIntArrayRegion方法要高很多
jbyte *arrp = env->GetByteArrayElements(javaArr, 0);
//另外两种方式
//signed char jbp1[lenght];
//signed char *jbp2 = env->GetByteArrayElements(javaArr, 0);
//对数组元素进行处理
for (int i = 0; i < lenght; i++) {
*(arrp + i) += i;
}
//将C数组中的元素拷贝到Java数组中
env->SetByteArrayRegion(javaArr, 0, lenght, arrp);
//如果需要,可以返回数组
//return javaArr;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java传一个byte[]到Jni,Jni对byte[]数据处理
public native void JavaJniTransByteArray1(byte[] arrByte);
}
JniActivity.java
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Java传一个byte[]到Jni,Jni对int[]元素进行修改
byte[] arrbyte1 = new byte[10];
Log.v(TAG, "BeforCallJni arrbyte1[]:" + Arrays.toString(arrbyte1));
mJniDemo.JavaJniTransByteArray1(arrbyte1);
Log.v(TAG, "AfterCallJni arrbyte1[]:" + Arrays.toString(arrbyte1));
}
}
日志打印:
(4).Java传一个byte[]到Jni,Jni拷贝数据到native byte[],处理数据后再返回native byte[]给Java
jnidemo.cpp
extern "C" {
JNIEXPORT jbyteArray JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaJniTransByteArray2(JNIEnv *env, jobject instance,
jbyteArray javaArr) {
//获取Java数组长度
int lenght = env->GetArrayLength(javaArr);
//新建一个jni byte指针,指向一块 byte数据内存
jbyte *jbp = (jbyte *) malloc(lenght * sizeof(jbyte));
//直接将Java端的数组拷贝到本地jni内存中
env->GetByteArrayRegion(javaArr, 0, lenght, jbp);
//对数组元素进行处理
for (int i = 0; i < lenght; i++) {
*(jbp + i) += i;
}
//新建一个jni byte数组
jbyteArray arrjb = env->NewByteArray(lenght);
//将jni byte指针所指内存中的元素拷贝到生成的C数组中,然后返回
env->SetByteArrayRegion(arrjb, 0, lenght, jbp);
//如果需要,可以返回数组
return arrjb;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java传一个byte[]到Jni,Jni拷贝数据到native byte[],处理数据后再返回native byte[]给Java
public native byte[] JavaJniTransByteArray2(byte[] arrByte);
}
JniActivity.java
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
byte[] arrbyte2 = new byte[10];
byte[] arrbyte3 = new byte[10];
Log.v(TAG, "BeforCallJni arrbyte2[]:" + Arrays.toString(arrbyte2));
Log.v(TAG, "BeforCallJni arrbyte3[]:" + Arrays.toString(arrbyte3));
arrbyte3 = mJniDemo.JavaJniTransByteArray2(arrbyte2);
Log.v(TAG, "AfterCallJni arrbyte2[]:" + Arrays.toString(arrbyte2));
Log.v(TAG, "BeforCallJni arrbyte3[]:" + Arrays.toString(arrbyte3));
}
}
日志打印:
新建了两个byte[],arrbyte2[] 和 arrbyte3[] 。
arrbyte2[] 作为参数传递到Jni,arrbyte3[] 用于被Jni返回的Native byte[]赋值
可以看到,arrbyte2[] ,arrbyte3[] 在调用JavaJniTransByteArray2()之前,都是初始值0,在调用之后,由于arrbyte2[]在Jni中其元素并没有被改变,所以打印出来仍然都是0,而arrbyte3[]元素值则是Jni返回的Native byte[]的元素值。
(5).Java调用Jni启动一个线程
jnidemo.h
extern "C" {
bool running = false;
void *JniThreadStartByJava(void *arg);
}
jnidemo.cpp
extern "C" {
/***** java 启动一个 jni 线程 *****/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaStartJNIThread(JNIEnv *env, jobject instance) {
pthread_t myThread;
int res = pthread_create(&myThread, NULL, JniThreadStartByJava, NULL);
if (res != 0) {
LOGW("JniThreadStartByJava create failed!");
return;
}
}
void *JniThreadStartByJava(void *arg) {
LOGW("JniThreadStartByJava create success!");
while (running) {
//do the thread thing...
}
return NULL;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java启动一个Jni线程
public native void JavaStartJNIThread();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//java启动一个jni线程
mJniDemo.JavaStartJNIThread();
}
}
日志打印:
可以看到,在 Java 成功的创建和启动 Jni 中的一个线程
3.2 Jni调用Java
(1).Jni调用Java非静态成员变量
jnidemo.cpp
extern "C" {
/**** jni访问java非静态成员变量 ****/
/* 1.使用 GetObjectClass、 FindClass获取调用对象的类
* 2.使用 GetFieldID 获取字段的ID。这里需要传入字段类型的签名描述。
* 3.使用 GetIntField、 GetObjectField等方法,获取字段的值。
* 4.使用 SetIntField、 SetObjectField等方法,设置字段的值。
* 注意:即使字段是 private也照样可以正常访问。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaNoStaticField(JNIEnv *env, jobject instance) {
//获取jclass
jclass j_class = env->GetObjectClass(instance);
//获取jfieldID
jfieldID j_fid = env->GetFieldID(j_class, "mNoStaticField", "I");
//获取java成员变量int值
jint j_int = env->GetIntField(instance, j_fid);
//noStaticField==0
LOGI("noStaticField==%d", j_int);
//Set<Type>Field 修改noStaticKeyValue的值改为111
env->SetIntField(instance, j_fid, 111);
}
}
JNIDEMO.java
public class JNIDEMO {
//非静态成员变量
public int mNoStaticField;
//Jni调用Java非静态成员变量
public native void JniCallJavaNoStaticField();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//jni调用Java非静态成员变量
Log.v(TAG, "BeforCallJni mJni.mNoStaticField:" + mJniDemo.mNoStaticField);
mJniDemo.JniCallJavaNoStaticField();
Log.v(TAG, "AfterCallJni mJni.mNoStaticField:" + mJniDemo.mNoStaticField);
}
}
日志打印:
可以看到,在Jni中对Java的非静态成员变量的值进行了改变
(2).Jni调用Java静态成员变量
jnidemo.cpp
Jni函数名前缀要与Java声明Native函数所在文件的包名+文件名对应
extern "C" {
/**** jni访问java静态成员变量 ****/
/* 1.使用 GetObjectClass、 FindClass获取调用对象的类
* 2.使用 GetStaticFieldID 获取字段的ID。这里需要传入字段类型的签名描述。
* 3.使用 GetStaticIntField、 GetStaticObjectField 等方法,获取字段的值。
* 4.使用 SetStaticIntField、 SetStaticObjectField 等方法,设置字段的值。
* 注意:即使字段是 private也照样可以正常访问。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaStaticField(JNIEnv *env, jobject instance) {
//获取jclass
jclass j_class = env->GetObjectClass(instance);
//获取jfieldID
jfieldID j_fid = env->GetStaticFieldID(j_class, "mStaticField", "I");
//获取java成员变量int值
jint j_int = env->GetStaticIntField(j_class, j_fid);
//noStaticField==0
LOGI("StaticField==%d", j_int);
//Set<Type>Field 修改noStaticKeyValue的值改为666
env->SetStaticIntField(j_class, j_fid, 222);
}
}
JNIDEMO.java
public class JNIDEMO {
//静态成员变量
public static int mStaticField;
//Jni调用Java静态成员变量
public native void JniCallJavaStaticField();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Jni调用Java静态成员变量
Log.v(TAG, "BeforCallJni mJni.mStaticField:" + mJniDemo.mStaticField);
mJniDemo.JniCallJavaStaticField();
Log.v(TAG, "AfterCallJni mJni.mStaticField:" + mJniDemo.mStaticField);
}
}
日志打印:
可以看到,在Jni中对Java的静态成员变量的值进行了改变
(3).Jni调用Java非静态成员方法
jnidemo.cpp
Jni函数名前缀要与Java声明Native函数所在文件的包名+文件名对应
extern "C" {
/**** jni调用java非静态成员方法 ****/
/* 1.使用 GetObjectClass、 FindClass获取调用对象的类
* 2.使用 GetMethodID获取方法的ID。这里需要传入方法的签名描述。
* 3.使用 CallVoidMethod执行无返回值的方法
* 4.使用 CallIntMethod、 CallBooleanMethod、CallStringMethod等执行有返回值的方法。
* 注意:即使字段是 private也照样可以正常访问。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaNoStaticMethod(JNIEnv *env, jobject instance) {
//回调JNI.java中的noParamMethod
jclass clazz = env->FindClass("com/android/demo/jni/JNIDEMO");
if (clazz == NULL) {
printf("find class Error");
return;
}
jmethodID method = env->GetMethodID(clazz, "noStaticMethod", "(I)I");
if (method == NULL) {
printf("find method Error");
return;
}
env->CallIntMethod(instance, method, 333);
}
}
JNIDEMO.java
public class JNIDEMO {
//非静态成员方法
private int noStaticMethod(int number) {
Log.v(TAG,"noStaticMethod() number: "+number);
return number;
}
//Jni调用Java非静态成员方法
public native void JniCallJavaNoStaticMethod();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Jni调用Java非静态成员方法
mJniDemo.JniCallJavaNoStaticMethod();
}
}
日志打印:
可以看到,Jni调用一个带整型参数的Java非静态成员方法,并且在Jni给这个方法传参333
运行后,这个Java非静态成员方法中的Log打印如下:
(4).Jni调用Java静态成员方法
jnidemo.cpp
Jni函数名前缀要与Java声明Native函数所在文件的包名+文件名对应
extern "C" {
/**** jni调用java静态成员方法 ****/
/*1.使用 GetObjectClass、 FindClass获取调用对象的类
* 2.使用 GetStaticMethodID 获取方法的ID。这里需要传入方法的签名描述。
* 3.使用 CallStaticVoidMethod 执行无返回值的方法。
* 4.使用 CallStaticIntMethod、 CallStaticBooleanMethod 等执行有返回值的方法。
* 注意:即使字段是 private也照样可以正常访问。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaStaticMethod(JNIEnv *env, jobject instance) {
//回调JNI.java中的noParamMethod
jclass clazz = env->FindClass("com/android/demo/jni/JNIDEMO");
if (clazz == NULL) {
printf("find class Error");
return;
}
jmethodID method = env->GetStaticMethodID(clazz, "staticMethod", "(I)I");
if (method == NULL) {
printf("find method Error");
return;
}
env->CallStaticIntMethod(clazz, method, 444);
}
}
JNIDEMO.java
public class JNIDEMO {
//静态成员方法
private static int staticMethod(int number) {
Log.v(TAG,"staticMethod() number: "+number);
return number;
}
//Jni调用Java静态成员方法
public native void JniCallJavaStaticMethod();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//jni调用Java静态成员方法
mJniDemo.JniCallJavaStaticMethod();
}
}
日志打印:
可以看到,Jni调用一个带整型参数的Java非静态成员方法,并且在Jni给这个方法传参333
运行后,这个Java非静态成员方法中的Log打印如下:
(5).Jni调用Java类的构造函数
新建一个JNIConstruct.java类,其中包含一个Int和一个String成员变量。
在Jni调用JNIConstruct.java类的构造函数,实现通过Jni调用构造函数传参,给这两个成员变量赋值
JNIConstruct.java
package com.android.demo.jni;
import android.util.Log;
public class JNIConstruct {
private final String TAG = "JNIConstruct";
private int paramInt = 0;
private String paramStr = null;
public JNIConstruct(int intp, String strp) {
paramInt = intp;
paramStr = strp;
}
public void printf() {
Log.v(TAG, "printf() paramInt:" + paramInt + " paramStr:" + paramStr);
}
}
jnidemo.cpp
Jni函数名前缀要与Java声明Native函数所在文件的包名+文件名对应
extern "C" {
/***** jni调用java构造方法 *****/
/* 1.使用 FindClass 获取需要构造的类
* 2.使用 GetMethodID 获取构造方法的ID。方法名为 <init>, 这里需要传入方法的签名描述。
* 3.使用 NewObject 执行创建对象。*/
JNIEXPORT jobject JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaConstructMethod(JNIEnv *env, jobject instance) {
// 1、获取 JNIConstruct 类的 class 引用
jclass cls_jniCons = env->FindClass("com/android/demo/jni/JNIConstruct");
if (cls_jniCons == NULL) {
return NULL;
}
// 2、获取 JNIConstruct 的构造方法ID (构造方法的名称统一为:<init>)
jmethodID med_jniCons = env->GetMethodID(cls_jniCons, "<init>", "(ILjava/lang/String;)V");
if (med_jniCons == NULL) {
return NULL; // 没有找到参数为int和String的构造方法
}
// 3、创建JNIConstruct对象的实例(调用对象的构造方法并初始化对象), env->NewStringUTF("") 创建一个 String 对象,作为构造方法的第二个 String 类型参数
jobject obj_jniCons = env->NewObject(cls_jniCons, med_jniCons, 555,
env->NewStringUTF("Jni Construct!"));
if (obj_jniCons == NULL) {
return NULL;
}
return obj_jniCons;
}
}
JNIDEMO.java
public class JNIDEMO {
//Jni调用Java类的构造函数
public native JNIConstruct JniCallJavaConstructMethod();
}
JniActivity.java
通过JNIDEMO实例对象调用声明的Native方法,实现对Jni函数的调用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//jni调用Java构造函数
JNIConstruct jniConstruct = mJniDemo.JniCallJavaConstructMethod();
jniConstruct.printf();
}
}
日志打印:
可以看到,Jni调用了JNIConstruct.java的构造函数,并传递两个参数:
- int paramInt = 555;
- String paramStr = Jni Construct!;
四.结束语
Jni 实战开发到此讲解完毕,篇幅有限,无法所有场景都实战涉及,但是万变不离其宗。
掌握了Jni理论基础,实践了多种类型Jni与Java互相调用后,其他都只是在此基础上的扩展了。