JAVA JNI开发流程概述

JNI开发流程:
1. 编写JAVA代码,并且在JAVA代码中申明需要用到的native方法
2. 编译JAVA代码,生成字节码文件    (javac)
3. 导出JAVA代码中的native方法,生成头文件    (javah)
4. 新建cpp源文件,含生成的头文件,在cpp源代码中实现native方法
5. 使用cpp源文件和头文件编译出so动态库
6. 在JAVA代码中通过System.LoadLibrary加载so文件

JavaVM: 在JNI中JavaVM代表的是Java虚拟机,每个Java进程有且只有一个全局的JavaVM对象,JavaVM对象可以跨线程共享
JNIEnv: 代表Java运行环境(上下文),一般是对应了Java的线程上下文,JNIEnv对象不能跨线程共享
JavaVM和JNIEnv内部都包含了一组Java虚拟机内部的函数指针,只是JavaVM和JNIEnv包含的函数指针不同。
JavaVM和JNIEnv只是一个统称,具体不同的JAVA虚拟机都有自己的实现,而对于外部,无论是C还是C++,都是以JJNIInvokeInterface和NINativeInterface指针的形式开放给用户的。

/*
 * JavaVM
 */
struct JNIInvokeInterface {
    // 一系列函数指针
    jint        (*DestroyJavaVM)(JavaVM*);
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
    jint        (*DetachCurrentThread)(JavaVM*);
    jint        (*GetEnv)(JavaVM*, void**, jint);
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};

/*
 * JNIEnv
 */
struct JNINativeInterface {
    // 一系列函数指针
    jint        (*GetVersion)(JNIEnv *);
    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize);
    jclass      (*FindClass)(JNIEnv*, const char*);
    ...
};


字符串:
1. JNIEnv提供GetStringUTFChars JNI方法将jstring引用转换为c/c++字符串,使用完后调用ReleaseStringChars释放
2. JNIEnv提供NewStringUTF JNI方法构造一个新的JAVA String字符串对象,不需要通过ReleaseStringChars释放


数组:
1. 基本类型有自己对应的array类型(jintArray, jbooleanArray等)
2. 引用类型的array类型为jobjectArray
3. JNIEnv提供了一组NewXXXArray(NewIntArray, NewBooleanArray...)接口来创建JAVA中的基本类型的数组
4. JNIEnv提供了一组GetXXXArrayElements(GetIntArrayElements,GetBooleanArrayElements等)接口用于将JAVA类型的数组转换为C/C++的数组


JNI中创建JAVA数组对象+赋值修改+返回的操作流程
1. 通过JNIEnv的NewXXXArray接口创建JAVA数组对象
2. 通过JNIEnv的GetXXXArrayElements(isCopy参数要设置为JNI_False,即复用模式,会修改原JAVA数组的内容)接口将刚才创建的JAVA数组对象转换成C/C++的数组(两者实际指向的是同一个对象)
3. 修改C/C++对象(例如赋值)
4. 通过JNIEnv的Release接口释放,同时因为isCopy设置为JNI_False,修改的内容会写回JAVA数组对象
5. 返回第一步创建的JAVA数组对象

JAVA中的属性和方法在class文件中都是由三个部分组成的(访问控制符 + 简单名称 + 描述符),其中最重要的是名称和描述符
名称:顾名思义就是函数名成/属性名称
描述符:分为字段描述符和方法描述符
    字段描述符:对应JAVA数据类型,每个JAVA数据类型都有对应的描述符

Java 类型描述符
booleanZ
byteB
charC
shortS
intI
longJ
floagF
doubleD
voidV
引用类型以 L 开头 ; 结尾,中间是 / 分隔的包名和类名。例如 String 的字段描述符为 Ljava/lang/String;

    方法描述符:通过字段描述符来完整描述方法的入参以及返回值,例如 ()V代表void func(),  (ZLjava/lang/String)V代表void func(boolean, String)
    
JNI访问JAVA对象中的字段(属性)
    获取属性的ID    (JNIEnv::GetFieldId(jclass, 字段名称, 字段描述符),如果是静态字段则需要用JNIEnv::GetStaticFieldId
    通过字段ID访问字段     (JNIEnv::GetXXXField(jobject thiz, 字段ID),GetXXXField根据要获取的字段的类型,例如GetIntField, GetBooleanField, GetStaticIntField等
    通过字段ID修改字段     (JNIEnv::SetXXXField(jobject thiz, 字段ID, value),  SetXXXField类似GetXXXField

步骤:
1. 通过JNI接口传入的jobject获取到this引用 jobject thiz
2. 使用JNIEnv::GetObjectClass获取this对象的类型对象jobject clz
3. 使用JNIEnv::GetFieldId从clz中获取属性的ID
4. 使用JNIEnv::GetXXXField/SetXXXField接口(传参 thiz + 属性ID)获取和设置属性值

JNI调用JAVA对象中的方法
   同访问JAVA对象的属性类似,也是需要先获取方法ID,然后才能调用方法
   1. JNIEnv::GetMethodId
   2. JNIEnv::Call<Type>Method  (Type对应返回类型)
  
引用:
JNI中的引用分为全部引用以及局部引用,这个引用就是java中的引用类型,引用了某个具体的java对象,释放引用类似与java语言中的=null操作
局部引用只在JNI方法内部有效,可以在方法结束前手动调用DeleteLocalRef函数释放对对象的局部引用,或者等方法结束时会自动释放该应用
全局引用在整个JVM中有效,因此,除非调用DeleteGlobalRef,否则引用一直存活(导致引用的对象不被GC回收)
局部引用不能跨方法,更不能跨线程访问

例如,有这样的场景,我们的某个业务JAVA类有配套的JNI层的cpp文件,在这个文件中希望保存所以有调用过该cpp文件中JNI函数的JAVA类的对象,那么就需要在JNI函数中,通过JNIEnv的NewGlobalRef函数,将局部应用(JNI函数的jobject参数)转换成JNI层的全局引用。

那么后面如果想要在JNI层的cpp文件中判断调用JNI接口的JAVA类对象是否之前已经调用过该JNI接口,那么需要通过JNIEnv的IsSameObject方法比对此次调用传入的jobject引用(引用了本次调用的java对象)和之前保存在JNI层的全局引用是否有匹配的。

JAVA ByteBuffer转C++字符串:

void saveString(JNIEnv* env, jobject thiz, jobject buffer, int size) // 第三个jobject就是JAVA层传入的ByteBuffer, 第四个参数是ByteBuffer的长度

{

    char* nativeMessage = reinterpret_cast<char*>(env->GetDirectBufferAddress(buffer);
    if (nativeMessage) {
        std::string msg(nativeMessage, nativeMessage + size);
    }
    ...

}

JAVA String转C++字符串:

void getJavaString(JNIEnv* env, jobject thiz, jstring str)
{
    const char* nativeString = env->GetStringUTFChars(str, nullptr);
    ...
    env->ReleaseStringUTFChars(str, nativeString);  // 不做这一步其实也可以,因为除了函数引用会被删除,不会导致java层的字符串无法被GC回收
}

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值