JNI一点理解

一,一些概念简介:

  • JNI,全称Java Native Interface,Java本地接口,是一套Java提供的API,随着JDK的诞生而诞生,可以用来调用本地方法,即在Java程序中声明但没有实现的方法,必须用native关键字声明,而方法的实现一般是放在C/C++ 程序里。JNI充当桥梁的角色,仍然属于Java层。为什么调用本地方法?因为性能更高,对内存管理更加细致,另外在Java诞生之处就存在很多本地方法,实现对这些方法的复用很重要,因此就有了JNI。
  • NDK,Android为了调用JNI而开发的一套工具集合,其中封装了JNI,并提供了GCC、G++等C/C++编译器。
  • 在这里插入图片描述
extern "C"  //表明以下代码使用C语言编译器编译,在C++环境下需要加上这个
JNIEXPORT   //表面该函数可以被外部调用
jstring     /*表面该函数的返回值为jstring类型,JNI为了链接Java和C/C++,充当翻译官的角色,提供了一些中间类型,其中jstring就是String、char *的中间类型*/
JNICALL     //约束函数的入站顺序,和堆栈内存清理规则
Java_com_example_as_1jni_MainActivity_stringFromJNI(  /*函数名,Java中对应本地方法的命名规则为:Java_包名_类名_方法名,如果有下划线,则变成_1, 例如 全限定类名.get_Name 变成全限定类名_get_1Name*/
 JNIEnv* env,  /*JNI的核心API,封装了Java的运行环境,在C语言中是二级指针,在C++中是一级指针C: (*env)->NewStringUTF(env,“abc”), C++:env->NewStringUTF(“abc”) 
另外,因为C语言中没有对象,所以调用函数必须加上对象env,而C++有对象,只需要指定函数的参数即可*/
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

其中最重要的就是JNIEnv* env,它是Java与C++沟通的桥梁,内置了很多方法,其中有获取类型的、对引用类型封装的、获取类型属性和方法的、调用对应类型方法的,使用反射机制,因此在Java在即使声明为private或者final也可以访问或者修改。
签名规则:
当获取或者使用原对象域的方法时,需要增加签名,因为有方法重载,签名是为了找到唯一对应的方法,签名规则如下:
在这里插入图片描述

二,JNI常用API总结

1,版本和类常用API
在这里插入图片描述
2,对象域API

2.1 GetFieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
函数作用:
  返回类的实例(非静态)域的域 ID。
参数说明:
  env:JNI 接口指针。
  clazz:Java 类对象。
  name: 0 终结的 UTF-8 字符串中的域名。
  sig:0 终结的 UTF-8 字符串中的域签名。

2.2 Set<type>Field

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
函数作用:
  该访问器例程系列设置对象的实例(非静态)域的值。要访问的域由通过调用SetFieldID() 而得到的域 ID 指定。
参数说明:  
  env:JNI 接口指针。
  obj:Java 对象(不能为 NULL)。
  fieldID:有效的域 ID。
value:域的新值。
2.3 Get<type>Field

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
函数作用:
  该访问器例程系列返回对象的实例(非静态)域的值。要访问的域由通过调用GetFieldID() 而得到的域 ID 指定。
参数说明:
  env:JNI 接口指针。
  obj:Java 对象(不能为 NULL)。
  fieldID:有效的域 ID。

<type>可以是Boolean、Char等类型,所有的Get<type>Field参考下面的函数

3,字符串相关API
在这里插入图片描述
4,数组相关API

4.1 GetArrayLength
jsize (*GetArrayLength)(JNIEnv*, jarray);
函数作用:返回数组中的元素数。

4.2 GetObjectArrayElement
jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize);
函数作用:返回 Object 数组的元素。

4.3 SetObjectArrayElement
void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject);
函数作用:设置 Object 数组的元素。
 
4.4 Get<PrimitiveType>ArrayRegion
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
函数作用:
  将基本类型数组某一区域复制到缓冲区中的一组函数。
参数说明:
  env:JNI 接口指针。
  array:Java 指针。
  start:起始下标。
  len:要复制的元素数。
buf:目的缓冲区。
4.5 Set<PrimitiveTyep>ArrayRegion

void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);

函数作用:
  将基本类型数组的某一区域从缓冲区中复制回来的一组函数。
参数说明:
  Env:JNI 接口指针。
  array:
  Java 数组。
  start:起始下标。
  len:要复制的元素数。
  buf:源缓冲区。

三,JNI的两种注册方式

要想Java方法调用本地方法,那就要将这两个方法之间建立关联,调用时找到对应的本地方法去执行,其实就是保存JNI方法的指针,在loadlibrary方法中加载so库,然后在此库中搜索对应的实现方法,分为静态注册和动态注册:
1,静态注册
Java中的一个方法可以限定为:包名-类名-方法名-方法参数,这样可以唯一的确定一个方法;那么如果JNI层根据某种规则这样构造方法,是不是也一一对应了?所以静态注册是怎么找到对应的方法去执行?依靠的是方法名。
静态注册基本流程:
首先,使用javac编译得到class文件,然后使用javah对class文件打包成.h文件,新建一个c++源文件,引入该.h头文件,然后在C++源文件里实现该native方法
2,动态注册
所谓的动态注册 是指,动态注册JAVA的Native方法,使得c/c++里面方法名 可以和 java 的Native方法名可以不同, 动态注册是将将二者方法名关联起来(维护一张静态表),以后在修改Native方法名时,只需修改动态注册关联的方法名称即可。 System.loadLibrary(“xxx”); 这个方法还是必须要调用的,不管动态还是静态,其作用主要就是调用JNI_Onload()方法,讲静态表注册给JNI环境。
动态注册基本流程:
(1),编写Java端的相关native方法
(2),编写C/C++代码, 实现JNI_Onload()方法,这是载入JNI库后调用的第一个方法,在这里可以将方法对应表注册给JNI环境
(3),将Java 方法和 C/C++方法通过签名信息一一对应起来
(4),通过JavaVM获取JNIEnv, JNIEnv主要用于获取Java类和调用一些JNI提供的方法
(5),使用类名和对应起来的方法作为参数, 层层调用,最终执行JNI提供的函数RegisterNatives()注册方法

3,二者区别
不管是静态注册还是动态注册,其作用都是将cpp文件编译成平台所需要的库。
静态注册
优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高
动态注册
优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高
缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

四,其他

1,垃圾回收
对于Java,有自动的垃圾回收,但在JNI这层没有,对用不到的资源应该尽量手动释放,以防止内存泄露问题,在JNI提供的三种引用方式,Local Reference(本地引用), Global Reference(全局引用), Weak Global Reference(全局弱引用),其中Global Reference,如果不手动释放,则会一直占用内存,另外两种也是
2,异常处理
在Java层出现异常,可以try-catch或者上抛给虚拟机,但在JNI层,并不会立即中断并抛出异常,还可以继续执行内存清理类的工作,知道返回到Java层才会抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值