JNI的数据类型及映射关系详解

JNI简介

JNI的数据类型及映射关系详解_第1张图片

JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植;从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互

本地代码与 Java 虚拟机之间是通过 JNI 函数实现相互操作的;JNI 函数通过接口指针来获得,本地方法将 JNI 接口指针当作参数来接受;虚拟机保证在从相同的 Java 线程中对本地方法进行多次调用时,传递给本地方法的接口指针是相同的,本地方法被不同的 Java 线程调用时,它接受不同的 JNI接口指针

使用JNI和算法进行交互,主要是为了提高算法的性能,最大化的利用机器硬件资源

JNI技术原理

JNI是Java NativeInterface(Java本地接口)的缩写;JNI作为java和操作系统间的一个直接接口,可以通过JNI使得java直接调用操作系统的资源

目前JNI只能通过c/C++实现,因为jni只是对操作系统资源调用的一个桥接过程;所以理论上在windows下只要是dll文件均可以被调用;java代码编译之后是运行在一个jvm里,所以java的任何操作对操作系统而言都是隔着一层虚拟机外壳,这点也正是java的优点,帮助java实现了“Write Once, Run Everywhere”的可移植性

但是使用了jni之后必须要明白这个“Write Once, Run Everywhere”要被打破,必须要实现不同的操作系统的各种jni版本

JNI的开发调用过程可以用下图来完整表示:

JNI的数据类型及映射关系详解_第2张图片

JNI 基本数据类型

JNI中数据类型分为两种,一种是基础数据类型,另一种是引用类型;在调用Java native方法将实参传递给C/C++函数的时候,会自动将java形参的数据类型自动转换成C/C++相应的数据类型,所以我们在写JNI程序的时候,必须要明白它们之间数据类型的对应关系;下面先介绍基础数据类型

JNI中的基本类型也称为Native基本类型

Java基本数据类型与JNI数据类型的映射关系如下:

Java数据类型 JNI数据类型 C/C++数据类型
boolean jboolean unsigned char
byte jbyte signed char
char jchar unsigned short
short jshort short
int jint int
long jlong long long
float jfloat float
double jdouble double
void void void

JNI String类型

修改 JniHello.java

/**
 * Created by act64 on 2017/5/25.
 */

public class JniHello {
    static{
        System.loadLibrary("nativehello");
    }


public static void main(String[]arg){
    JniHello jniHello =new JniHello();
    System.out.println( jniHello.hello("world"));

}
    public native String hello(String str);
}

为了方便搞明白数据类型和参数以及对于的描述符,还是使用javah生成

//在terminal里面输入
javac JniHello.java
javah JniHello

这样目录下就生成了JniHello.h文件
打开 JniHello.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class JniHello */

#ifndef _Included_JniHello
#define _Included_JniHello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JniHello
 * Method:    hello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JniHello_hello
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

我们在意的是Signature 和JNICALL Java_JniHello_hello的参数和返回值,将这些参数运用到hello.c中

#include 
#include 
jstring c_hello(JNIEnv * env, jobject mJobject,jstring val){
    const jbyte *str;
    str = (*env)->GetStringUTFChars(env, val, NULL);
    if (str == NULL) {
        return NULL; /* OutOfMemoryError already thrown */
    }
    printf("java IN %s", str);
    (*env)->ReleaseStringUTFChars(env, val, str);

    return (*env)->NewStringUTF(env, "helloJNIStr");
}

const JNINativeMethod methods[]={
    {"hello","(Ljava/lang/String;)Ljava/lang/String;",(jstring *)c_hello},
};


JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *env;
    jclass cls;
    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
    }
    cls = (*env)->FindClass(env, "JniHello");
    if (cls == NULL) {
        return JNI_ERR;
    }

    if((*env)->RegisterNatives(env,cls,methods,1)<0){
        return JNI_ERR;
    }
    return JNI_VERSION_1_4;
}

然后编译文件

 gcc -fPIC -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -shared -o libnativehello.so  hello.c

export LD_LIBRARY_PATH=./

javac JniHello.java

java JniHello

结果如下


完整代码点击底层源码免费获取

JNI的两个优点

JNI解决本机平台接口调用问题

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能

解决JAVA对本地操作的一种方法就是JNI;JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在 WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系, 调用系统级的各接口方法

NI嵌入式开发应用

一次编程,到处使用”的Java软件概念原本就是针对网上嵌入式小设备提出的,几经周折,目前SUN公司已推出了 J2ME(Java 2 P1atform Micro Edition)针对信息家电的Java版本,其技术日趋成熟,开始投入使用

SUN公司Java虚拟机(JVM)技术的有序开放,使得Java软件真正 实现跨平台运行,即Java应用小程序能够在带有JVM的任何硬软件系统上执行;加上Java语言本身所具有的安全性、可靠性和可移植性等特点,对实现瘦 身上网的信息家电等网络设备十分有利,同时对嵌入式设备特别是上网设备软件编程技术产生了很大的影响。也正是由于JNI解决了本机平台接口调用问题,于是 JNI在嵌入式开发领域也是如火如荼

JNI的局部、全局引用及弱全局引用

局部引用

● 通过 NewLocalRef 和各种 JNI 接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等)

● 会阻止 GC 回收所引用的对象,不在本地函数中跨函数使用,不能跨线程使用。函数返回后局部引用所引用的对象会被JVM 自动释放,或调用 DeleteLocalRef 释放。(*env)->DeleteLocalRef(env,local_ref)

全局引用

● 调用 NewGlobalRef 基于局部引用创建,会阻 GC 回收所引用的对象

● 可以跨方法、跨线程使用。JVM 不会自动释放,必须调用 DeleteGlobalRef 手动释放。(*env)->DeleteGlobalRef(env,g_cls_string)

弱全局引用

● 调用 NewWeakGlobalRef 基于局部引用或全局引用创建,不会阻止 GC 回收所引用的对象,可以跨方法、跨线程使用

● 引用不会自动释放,在 JVM 认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef 手动释放。(*env)->DeleteWeakGlobalRef(env,g_cls_string)

局部引用

大多数JNI函数返回局部引用。局部引用不能在后续的调用中被缓存及重用,主要是因为它们的使用期限仅限于原生方法,一旦原生函数返回,局部引用即被释放

例如,使用FindClass函数返回一个局部引用,当原生方法返回时,它被自动释放,也可以用DeleteLocalRef函数显式释放原生代码:

jclass clazz
clazz = (*env)->FindClass(env,"java/lang/String");
……
(*env)->DeleteLocalRef(env,clazz);

根据JNI的规范,虚拟机应该允许原生代码创建最少16个局部引用

全局引用

全局引用在原生方法的后续调用过程中依然有效,除非它们被原生代码显示释放

创建全局引用

可以用NewGlobalRef函数将局部引用初始化为全局引用,例如:

1 jclass localclazz
2 jclass globalclazz
3 ……
4 localclazz = (*env)->FindClass(env,"java/lang/String");
5 globalclazz = (*env)->NewGlobalRef(env,localclazz );
6 ……
7 (*env)->DeleteLocalRef(env,localclazz );

删除全局引用

当原生代码不再需要一个全局引用时,可以随时用DeleteLocalRef函数释放它

(*env)->DeleteLocalRef(env,globalclazz );

弱全局引用

弱全局引用和全局引用一样,在原生方法的后续调用过程中依然有效。与全局引用不同,弱全局引用并不阻止潜在的对象被垃圾回收

创建弱全局引用

用NewWeakGlobalRef函数对弱全局引用进行初始化,例如:

1 jclass weakGlobalclazz
2 weakGlobalclazz = (*env)->NewWeakGlobalRef(env,localclazz);

弱全局引用的有效性校验

可以使用IsSameObject函数检验一个弱全局引用是否仍然指向活动的类实例。如果基本对象没被垃圾收集,则返回JNI_FALSE,否则返回JNI_TRUE。例如:

1 if(JNI_FALSE == (*env)->IsSameObject(env,weakGlobalClazz,NULL)){
2 /*对象仍然处于活动状态且可以使用*/
3 }else{
4 /*对象被垃圾回收期收回,不能使用*/
5 }

删除弱全局引用

可以随时使用DeleteWeakGlobalRef函数释放弱全局引用。

(*env)->DeleteLocalRef(env,weakGlobalClazz);

尾述

需要完整代码的同学可以点击底层源码免费获取

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

PS:有问题欢迎指正,可以在评论区留下你的建议和感受;
欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下

你可能感兴趣的:(Android,开发,Android,工程师,移动开发,java,开发语言,android)