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

news2024/10/6 22:20:26

一.概述

上一篇博文讲解了如何搭建一个可以加载和链接第三方库、编译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;
#else
 typedef 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获取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[] 作为参数传递到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();

   }
}

日志打印:

可以看到,在 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互相调用后,其他都只是在此基础上的扩展了。

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

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

相关文章

== 和 equals:对象相等性比较的细微差别

和 equals&#xff1a;对象相等性比较的细微差别 既要脚踏实地于现实生活&#xff0c;又要不时跳出现实到理想的高台上张望一眼。在精神世界里建立起一套丰满的体系&#xff0c;引领我们不迷失不懈怠。待我们一觉醒来&#xff0c;跌落在现实中的时候&#xff0c;可以毫无怨言地…

Minio 判断对象是否存在

引 Minio数据模型 中描述了 MinIO 中什么是桶&#xff0c;什么是对象&#xff0c;也给出了操作桶和操作对象的API。 在 MinIO 中&#xff0c; 对象 中间前缀 对象名称 。如何判定对象是否存在呢&#xff1f; 分析 在 MinIO 中并没有提供判断对象是否存在的操作&#xff…

VS Code Json格式化插件-JSON formatter

&#x1f9aa;整个文件格式化 按快捷键Shift Alt F &#x1f96a;仅格式化选择内容 需要选择完整的json段落即&#xff1a;{} 或 [] 括起来的部分&#xff0c;再按快捷键Ctrl K F

激光雷达行业梳理1-概述、市场、技术路线

激光雷达作为现代精确测距和感知技术的关键组成部分&#xff0c;在近几年里取得了令人瞩目的发展。作为自动驾驶感知层面的重要一环&#xff0c;相较摄像头、毫米波雷达等其他传感器具有“ 精准、快速、高效作业”的巨大优势&#xff0c;已成为自动驾驶的主传感器之一&#xff…

芋道--如何自定义业务表单,配置对应的工作流程(详细步骤)

需求描述: 芋道的动态表单就不再介绍了&#xff0c;相对来讲比较简单,跟着官网文档就可以实现&#xff0c;本文将详细的介绍如何新建独立的业务表记录申请的信息&#xff0c;并设计对应的工作流。 这里表中的每一条记录&#xff0c;都将通过流程实例编号(process_instance_id )…

mysql-进阶篇

文章目录 存储引擎MySQL体系结构相关操作 存储引擎特点InnoDBInnoDB 逻辑存储结构 MyISAMMemory三个存储引擎之间的区别存储引擎的选择 索引1. 索引结构B-TreeB-Tree (多路平衡查找树)B-Tree演变过程 BTree与 B-Tree 的区别BTree演变过程 Hash 2.索引分类3.索引语法演示 4.SQL性…

946. 验证栈序列(力扣)

946. 验证栈序列 Problem: 946. 验证栈序列 文章目录 思路解题方法复杂度Code 思路 对栈的使用 解题方法 1.我们可以通过把pushed重新一个一个入我们自己创建的栈如果某次入栈碰到与poped第一个元素相同的那我们就对poped出栈处理(即跳过第一个元素);如此循环,直到我们的栈到最…

【C++记忆站】类和对象(二)

类和对象(二) 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的成员…

使用DTS实现TiDB到GaiaDB数据迁移

1 概览 本文主要介绍通过 DTS 数据迁移功能&#xff0c;结合消息服务 for Kafka 与 TiDB 数据库的 Pump、Drainer 组件&#xff0c;完成从TiDB迁移至百度智能云云原生数据库 GaiaDB。 消息服务 for Kafka&#xff1a;详细介绍参见&#xff1a;消息服务 for Kafka 产品介绍百度智…

Ubuntu Desktop 隐藏 / 显示文件和文件夹

Ubuntu Desktop 隐藏 / 显示文件和文件夹 1. GUI hot key2. Show hidden and backup filesReferences 1. GUI hot key Ctrl H: 隐藏 / 显示文件和文件夹 2. Show hidden and backup files Edit -> Preferences -> Views References [1] Yongqiang Cheng, https://yo…

AI大概不会很快抢走你的饭碗哦!

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

交互式AI百舸争流,声通科技要再次破题实现IPO?

仅隔半年&#xff0c;声通科技两次递表可以窥见其上市势在必行的决心。 事实也确实如此&#xff0c;由于对赌期限在即&#xff0c;声通科技上市迫在眉睫。 招股书显示&#xff0c;若声通科技未能于2024年12月31日之前完成合资格首次公开发售。那么声通科技及其创始人将赎回已…

Redis服务端优化(持久化配置、慢查询、命令及安全配置、内存配置)

文章目录 持久化配置慢查询命令及安全配置内存配置 持久化配置 慢查询 命令及安全配置 漏洞&#xff1a;Redis未授权访问配合SSH key文件利用分析-腾讯云开发者社区-腾讯云 (tencent.com) 漏洞出现的核心的原因有以下几点 Redis未设置密码利用了Redis的config set命令动态修…

Go 基本数据

第 2 章 基本数据类型 Go 的数值类型包括了不同大小的整数 、浮点数 、复数&#xff1b; 各种数值类型分别有自己的大小&#xff0c;对正负号支持也各不相同&#xff1b; 1. 整数&#xff08;OK&#xff09; 整数类型&#xff08;整型&#xff09;整数类型Go 语言同时支持 有…

VUE---插槽

一、插槽的作用&场景 1、在封装组件的时候&#xff0c;将可变的结构设计为插槽&#xff08;<slot></slot>&#xff09; 2、使用上述组件的时候&#xff0c;可以按需为插槽提供自定义的结构&#xff0c;以达到复用组件且高度自定的效果 二、基本语法 1、组件内…

关于网络安全 的 ARP欺骗 实验操作

实验设备&#xff1a; Windows server 2008 kali 1. vmware--上面菜单栏--虚拟机--设置--网络--NAT 模式 确定靶机与攻击机的连通性&#xff08;互相能 ping 通&#xff09; 靶机查看 arp 表&#xff08;arp -a&#xff09; 查看攻击机(kali)物理地址&#xff08;ip addr&…

MIT 6s081 lab4.xv6进程调度

xv6进程调度 在xv6中&#xff0c;调度发生的两种情况&#xff1a; 时钟中断导致的进程切换&#xff08;也叫时间片轮转&#xff09;睡眠锁&#xff0c;当进程调用sleep时&#xff0c;发生cpu的调度 xv6进程相关概念 xv6用struct proc来描述进程 // Per-process state stru…

uvicorn日志清空问题以及uvicorn日志配置

uvicorn日志清空问题 1、配置&#xff1a; uvicorn starlette 2、现象描述&#xff1a; 当我使用uvicorn starlette进行Python web开发的时候&#xff0c;本来想把所有的日志都打印到一个文件里面&#xff0c;于是我写了一个启动脚本&#xff0c;所有的日志都输出到log.t…

最大流-Dinic算法,原理详解,四大优化,详细代码

文章目录 零、前言一、概念回顾(可略过)1.1流网络1.2流1.3最大流1.4残留网络1.5增广路径1.6流网络的割1.7最大流最小割定理1.7.1证明 1.8Ford-Fulkerson方法 二、Dinic算法2.1EK算法的可优化之处2.2Dinic算法的优化策略2.3Dinic算法原理2.3.1找增广路2.3.2更新剩余容量 2.4算法…

浏览器无网

目录 1.运行网络诊断&#xff0c;确认原因 原因A.远程计算机或设备将不接受连接(该设备或资源(Web 代理)未设置为接受端口“7890”上的连接 原因B.DNS服务器未响应 场景A.其他的浏览器可以打开网页&#xff0c;自带的Edge却不行 方法A&#xff1a;关闭代理 Google自带翻译…