目录
什么是NDK
Android NDK 是一套工具集合,允许你使用C/C++语言来实现应用程序的部分功能。NDK本身其实就是一个交叉工作链,包含了Android上的一些库文件,然后,NDK为了方便使用,提供了一些脚本,使得更容易的编译C/C++代码。
什么是JNI
JNI,全称为Java Native Interface,即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。
JNI的命名规则
JNIExport jstring JNICALL com_example_hellojni_MainActivity_stringFromJNI(JNIEnv* env, jobject obj)
jstring
是返回值类型com_example_hellojni
是包名MainActivity
是类名stringFromJNI
是方法名
JNIExport
和 JNICALL
是定义在jni.h头文件中的宏定义,声明该JNI函数可从动态库导出和符合调用约定;
JNIEnv 一个接口指针,是JNI环境结构体指针(C语言版本),使用它能完成很多与java交互的操作;
jobject 表示Java层native方法的调用对象(此处是obj对象)。
如何实现JNI
JNI开发流程的步骤:
- 第1步:在Java中先声明一个native方法
- 第2步:编译Java源文件javac得到.class文件
- 第3步:通过javah -jni命令导出JNI的.h头文件
- 第4步:使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)
- 第5步:将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib)
- 第6步:通过Java命令执行Java程序,最终实现Java调用本地代码。
注:javah 是JDK自带的一个命令,-jni参数表示将class 中用到native 声明的函数生成JNI 规则的函数。
例:在Terminal下首先定位到src目录下,然后在输入
javah -jni com.example.hellojni.MainActivity
使用javah生成Native方法对应的Native函数声明,会发现所有的Native函数的第一个参数永远是JNIEnv指针,而第二个参数永远是jobject或jclass中的一个。JNIEnv指针指代何物?具有何种功能?jobject和jclass又有何区别?
JNIEnv指针
JNIEnv,顾名思义,指代了Java本地接口环境(Java Native Interface Environment),是一个JNI接口指针,指向了本地方法的一个函数表,该函数表中的每一个成员指向了一个JNI函数,本地方法通过JNI函数来访问JVM中的数据结构。Java可以创建多个线程,每个线程对应一个JNIEnv结构,所以JNI接口指针仅在当前线程中起作用。这意味着指针不能从一个线程进入另一个线程。
JNIEnv的作用
调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码
操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象
JNIEnv的创建
C 中的创建:JNIInvokeInterface是C语言环境中的JavaVM结构体,调用 AttachCurrentThread(JavaVM, JNIEnv*, void) 方法,能够获得JNIEnv结构体。
C++中的创建:_JavaVM是C++中JavaVM结构体,调用jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 方法,能够获取JNIEnv结构体。
JNIEnv的释放
C 中释放:调用JavaVM结构体JNIInvokeInterface中的(DetachCurrentThread)(JavaVM)方法,能够释放本线程的JNIEnv
C++ 中释放:调用JavaVM结构体_JavaVM中的jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); } 方法,就可以释放 本线程的JNIEnv
了解JNI函数
首先看看什么是JNI函数。JNI函数就是在native层定义的本地函数,对应于在java层使用native关键字声明的方法的。直白的说,就是在Java层声明,C/C++语言实现的。当然,这个函数并不一般,它会通过JNI某种机制与Java层的方法进行关联,使得Java层代码可以很方便的调用它。
jobject 与 jclass类型
jobject 与 jclass 通常作为 JNI函数 的第二个参数,当所声明Native方法是静态方法时,对应参数jclass
,因为静态方法不依赖对象实例,而依赖于类,所以参数中传递的是一个jclass
类型。相反,如果声明的Native方法时非静态方法时,那么对应参数是jobject
。为了能够在Native层访问Java中的类和对象,jobject
和 jclass
分别指代了其所指代的对象和类,进而访问成员方法和成员变量等。
JNI数据类型
由于Java语言与C/C++语言数据类型的不匹配,需要单独定义一系列的数据类型转换关系来完成两者之间的映射。
基本类型对照表
Java类型 | JNI类型 | 描述 |
---|---|---|
boolean | jboolean | 无符号8位 |
byte | jbyte | 无符号8位 |
char | jchar | 无符号16位 |
short | jshort | 有符号16位 |
int | jint | 有符号32位 |
long | jlong | 有符号64位 |
float | jfloat | 有符号32位 |
double | jdouble | 有符号64位 |
void | void | 无类型 |
引用类型对照表
Java类型 | JNI类型 | 描述 |
---|---|---|
boolean[] | jbooleanArray | 布尔类型数组 |
byte[] | jbyteArray | 字节数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 单精度浮点型数组 |
double[] | jdoubleArray | 双精度浮点型数组 |
All objects | jobject | 任何 Java 对象 |
Object[] | jobjectArray | 对象数组 |
java.lang.Class | jclass | Class 对象 |
java.lang.String | jstring | 字符串对象 |
java.lang.Throwable | jthrowable | Throwable 对象 |
签名机制
函数签名由字符串组成,第一部分是包含在圆括号()里的,用来说明参数类型,第二部分则跟的是返回值类型,[表示数组
。比如”([Ljava/lang/Object;)Z”就是参数为Object[],返回值是boolean的函数的签名。下表列出类型与签名标识的对应关系:
Java类型 | 类型标识 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double |