Android:JNI实战,理论详解、Java与Jni数据调用

一.概述

上一篇博文讲解了如何搭建一个可以加载和链接第三方库、编译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 的类型定义在 CC++ 中略有不同,但本质上是相同的,内部由一系列指向虚拟机内部函数指针组成。

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;#if defined(__cplusplus)typedef _JNIEnv JNIEnv;typedef _JavaVM JavaVM;
#elsetypedef const struct JNINativeInterface* JNIEnv;typedef const struct JNIInvokeInterface* JavaVM;
#endif

结构体详细定义可翻阅 Jni.h 头文件,在此不作代码列举

2.2.Jni 基础数据类型:

Jni 的数据类型都在 jni.h 头文件中定义,包括基础数据类型int 等)和引用数据类型ObjectClass数组等)

(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 类型描述长度(字节)
booleanjbooleanunsigned char1
bytejbytesigned char1
charjcharunsigned short2
shortjshortsigned short2
intjint、jsizesigned int4
longjlongsigned long8
floatjfloatsigned float4
doublejdoublesigned double8
ClassjclassClass 类对象1
Stringjstrting字符串对象/
Objectjobject对象/
Throwablejthrowable异常对象/
boolean[]jbooleanArray布尔数组/
byte[]jbyteArraybyte 数组/
char[]jcharArraychar 数组/
short[]jshortArrayshort 数组/
int[]jinitArrayint 数组/
long[]jlongArraylong 数组/
float[]jfloatArrayfloat 数组/
double[]jdoubleArraydouble 数组/

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 类型描述符
booleanZ
byteB
charC
shortS
intI
longJ
floagF
doubleD
voidV
引用类型以 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获取Stringpublic 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[]给Javapublic 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[] 作为参数传递到Jniarrbyte3[] 用于被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();}
}

日志打印:

可以看到,在Jni中对Java非静态成员变量的值进行了改变

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) {//获取jclassjclass j_class = env->GetObjectClass(instance);//获取jfieldIDjfieldID j_fid = env->GetFieldID(j_class, "mNoStaticField", "I");//获取java成员变量int值jint j_int = env->GetIntField(instance, j_fid);//noStaticField==0LOGI("noStaticField==%d", j_int);//Set<Type>Field 修改noStaticKeyValue的值改为111env->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) {//获取jclassjclass j_class = env->GetObjectClass(instance);//获取jfieldIDjfieldID j_fid = env->GetStaticFieldID(j_class, "mStaticField", "I");//获取java成员变量int值jint j_int = env->GetStaticIntField(j_class, j_fid);//noStaticField==0LOGI("StaticField==%d", j_int);//Set<Type>Field 修改noStaticKeyValue的值改为666env->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中的noParamMethodjclass 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中的noParamMethodjclass 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; // 没有找到只有一个参数为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互相调用后,其他都只是在此基础上的扩展了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/641260.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab appdesigner系列-常用17-编辑字段(数值、文本)

编辑字段&#xff08;数值、文本&#xff09;可直接键入数值、文本&#xff0c;其他组件直接调用其值。也可以利用把其他组件回调的值&#xff0c;返回到编辑字段&#xff08;数值、文本&#xff09;进行显示。 示例&#xff1a;利用按钮组件改变编辑字段&#xff08;数值&…

详解APQC流程分级分类框架PCF13个高阶分类和5级业务流程

一&#xff1a;什么是APQC 美国生产力与质量中心(American Productivity and Quality Center&#xff0c;简称为APQC)&#xff0c;创立于1977年是一个会员制的非营利机构&#xff0c;使命是“发现有效的改进方法&#xff0c;广泛地传播其发现成果&#xff0c;实现个人之间及其…

etcd基本介绍

etcd基本介绍 ETCD是SoreOs公司发布的一个分布式的、高可用的、key-value存储的数据库。基于Go语言实现&#xff0c;k8s中也使用了ETCD作为数据库。主要用于共享配置和服务发现。相对于zookeeper采用的Paxos&#xff0c;ETCD采用的是Raft算法&#xff0c;该算法具备的性能更佳…

Centos7 安装redis 详细步骤访问不了github和windows系统下载

windows系统下载 https://hellowindows.cn/ VMware虚拟机安装Windows Server 2016 VL https://blog.csdn.net/qq_37545849/article/details/134828341 VMware全屏时不显示上方命令栏的边缘 此时如果要返回&#xff0c;可以把鼠标移动至屏幕上方边缘短暂停留以呼出命令栏。或使…

第二篇【传奇开心果短博文系列】Python的OpenCV库技术点案例示例:图像处理

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例短博文系列 博文目录一、项目目标二、第一个示例代码三、第二个示例代码四、第三个示例代码五、第四个示例代码六、第五个示例代码七、知识点归纳总结 系列短博文目录 Python的OpenCV库技术点案例示例短博文…

OPENGL光线追踪

纪念一下运行出了光线追踪代码,用了glfw和glad。 光线的数学表达式 一条光线实际上只是一个起点和一个传播方向&#xff0c;因此光线表达式为&#xff1a; p(t) e t (s-e) 光线与球相交 已知球体的隐式方程为&#xff1a; 把光线 p(t) e t d 代入上述方程&#xff0c;得…

基于SpringBoot Vue医院门诊管理系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

RPC和HTTP,它们之间到底啥关系

既然有 HTTP 请求&#xff0c;为什么还要用 RPC 调用&#xff1f; gPRC 为什么使用 HTTP/2 Spring Cloud 默认是微服务通过Restful API来进行互相调用各自微服务的方法&#xff0c;同时也支持集成第三方RPC框架&#xff08;这里的说的RPC是特指在一个应用中调用另一个应用的接…

Git--基本操作介绍(2)

Git 常用的是以下 6 个命令&#xff1a;git clone、git push、git add 、git commit、git checkout、git pull. 说明&#xff1a; workspace&#xff1a;工作区staging area&#xff1a;暂存区/缓存区local repository&#xff1a;版本库或本地仓库remote repository&#xf…

Linux 一键部署grafana

grafana 前言 Grafana 是一款开源的数据可视化和监控仪表盘工具。它提供了丰富的数据查询、可视化和报警功能,可用于实时监控、数据分析和故障排除等领域。 通过 Grafana,您可以连接到各种不同的数据源,包括时序数据库(如 Prometheus、InfluxDB)和关系型数据库(如 MySQ…

2008年苏州大学837复试机试C/C++

2008年苏州大学复试机试 题目 编写程序充成以下功能: 一、从键盘上输入随机变量x的 10个取样点。X0&#xff0c;X1—X9 的值; 1、计算样本平均值 2、判定x是否为等差数列 3、用以下公式计算z的值(t0.63) 注。请对程序中必要地方进行注释 补充&#xff1a;个人觉得这个题目回忆…

Vscode 顶部Menu(菜单)栏消失如何恢复

Vscode 顶部Menu(菜单)栏消失如何恢复&#xff1f; 首先按一下 Alt按键&#xff0c;看一下是否恢复了菜单栏如果恢复了想了解更进一步的设置&#xff0c;或是没能恢复菜单栏&#xff0c;可以看后续。 1.首先点击左下角 齿轮&#xff0c;打开settings; 或者 直接 ctrl 逗号 …

chrome提升搜索效率的快捷方法

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

C# 使用System.Threading.Timer 实现计时器

写在前面 以往一般都是用 System.Timers.Timer 来做计时器&#xff0c;而 System.Threading.Timer 也可以实现计时器功能&#xff0c;并且还可以配置首次执行间隔&#xff0c;在功能上比System.Timers.Timer更加丰富&#xff1b;根据这个特性就可以实现按指定时间间隔对委托进…

书法家深入社区开展“迎春送福”写春联活动

春节将至&#xff0c;岁寒情深。2024年1月22日&#xff0c;在春节即将来临之际&#xff0c;双岗街道万小店社区邀请“四知书画院”院长杨东初、“东方诗书画院”院长杨玉能、“林散之草圣书画院”客座教授倪萍等知名书法家&#xff0c;在合肥市庐阳区为民社会服务中心开展“迎春…

Feign代理目标方法执行流程

总体而言Feign调用目标方法之前被jdk动态代理区分为两种形式&#xff1a;负载均衡方式以及域名直接调用方式。 public class FeignClientFactoryBean{public <T> T getTarget() {//通过父容器创建子容器工厂类FeignContextFeignContext context applicationContext.get…

ARM_Linux中GCC编译器的使用

目录 前言: GCC编译过程: 预处理&#xff1a; 编译阶段&#xff1a; 汇编&#xff1a; 链接阶段 GCC的常见使用 前言: 什么是GCC: gcc的全称是GNU Compiler Collection&#xff0c;它是一个能够编译多种语言的编译器。最开始gcc是作为C语言的编译器&#xff08;GNU C Co…

PgSQL - 17新特性 - 块级别增量备份

PgSQL - 17新特性 - 块级别增量备份 PgSQL可通过pg_basebackup进行全量备份。在构建复制关系时&#xff0c;创建备机时需要通过pg_basebackup全量拉取一个备份&#xff0c;形成一个mirror。但很多场景下&#xff0c;我们往往不需要进行全量备份/恢复&#xff0c;数据量特别大的…

C++面试宝典第23题:乌托邦树

题目 乌托邦树每年经历2个生长周期。每年春天,它的高度都会翻倍。每年夏天,他的高度都会增加1米。对于一颗在春天开始时种下的高为1米的树,问经过指定周期后,树的高度为多少? 输入描述:输入一个数字N(0 <= N <= 1000),表示指定周期。 比如:样例输入为3。 输出描…

helm---自动化一键部署

什么是helm?? 在没有这个helm之前&#xff0c;deployment service ingress helm的作用就是通过打包的方式&#xff0c;把deployment service ingress 这些打包在一块&#xff0c;一键式部署服务&#xff0c;类似于yum 官方提供的一个类似于安装仓库的功能&#xff0c;可以实…