JNI概述
JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。
Android NDK(Native Development Kit )是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。
JNI和NDK的区别?
- 从工具上说,NDK其实多了一个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置。
- 从编译库说,NDK开发C/C++只能能使用NDK自带的有限的头文件,而使用JNI则可以使用文件系统中带的头文件。
- 从编写方式说,它们一样。
JNI 元素
JNI组织结构
JNI函数表的组成就像C++的虚函数表,虚拟机可以运行多张函数表。
JNI接口指针仅在当前线程中起作用,指针不能从一个线程进入另一个线程,但可以在不同的线程中调用本地方法。
原始数据
jobject 对象引用类型
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 | N/A |
函数操作
函数 | Java 数组类型 | 本地类型 | 说明 |
---|---|---|---|
GetBooleanArrayElements | jbooleanArray | jboolean | ReleaseBooleanArrayElements 释放 |
GetByteArrayElements | jbyteArray | jbyte | ReleaseByteArrayElements 释放 |
GetCharArrayElements | jcharArray | jchar | ReleaseShortArrayElements 释放 |
GetShortArrayElements | jshortArray | jshort | ReleaseBooleanArrayElements 释放 |
GetIntArrayElements | jintArray | jint | ReleaseIntArrayElements 释放 |
GetLongArrayElements | jlongArray | jlong | ReleaseLongArrayElements 释放 |
GetFloatArrayElements | jfloatArray | jfloat | ReleaseFloatArrayElements 释放 |
GetDoubleArrayElements | jdoubleArray | jdouble | ReleaseDoubleArrayElements 释放 |
GetObjectArrayElement | 自定义对象 | object | |
SetObjectArrayElement | 自定义对象 | object | |
GetArrayLength | 获取数组大小 | ||
New<Type>Array |
创建一个指定长度的原始数据类型的数组 | ||
GetPrimitiveArrayCritical | 得到指向原始数据类型内容的指针,该方法可能使垃圾回收不能执行,该方法可能返回数组的拷贝,因此必须释放此资源。 | ||
ReleasePrimitiveArrayCritical | 释放指向原始数据类型内容的指针,该方法可能使垃圾回收不能执行,该方法可能返回数组的拷贝,因此必须释放此资源。 | ||
NewStringUTF | jstring类型的方法转换 | ||
GetStringUTFChars | jstring类型的方法转换 | ||
DefineClass | 从原始类数据的缓冲区中加载类 | ||
FindClass | 该函数用于加载本地定义的类。它将搜索由CLASSPATH 环境变量为具有指定名称的类所指定的目录和 zip文件 | ||
GetObjectClass | 通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL | ||
GetSuperclass | 获取父类或者说超类 。 如果 clazz 代表类class而非类 object,则该函数返回由 clazz 所指定的类的超类。 如果 clazz指定类 object 或代表某个接口,则该函数返回NULL | ||
IsAssignableFrom | 确定 clazz1 的对象是否可安全地强制转换为clazz2 | ||
Throw | 抛出 java.lang.Throwable 对象 | ||
ThrowNew | 利用指定类的消息(由 message 指定)构造异常对象并抛出该异常 | ||
ExceptionOccurred | 确定是否某个异常正被抛出。在平台相关代码调用 ExceptionClear() 或 Java 代码处理该异常前,异常将始终保持抛出状态 | ||
ExceptionDescribe | 将异常及堆栈的回溯输出到系统错误报告信道(例如 stderr)。该例程可便利调试操作 | ||
ExceptionClear | 清除当前抛出的任何异常。如果当前无异常,则此例程不产生任何效果 | ||
FatalError | 抛出致命错误并且不希望虚拟机进行修复。该函数无返回值 | ||
NewGlobalRef | 创建 obj 参数所引用对象的新全局引用。obj 参数既可以是全局引用,也可以是局部引用。全局引用通过调用DeleteGlobalRef() 来显式撤消。 | ||
DeleteGlobalRef | 删除 globalRef 所指向的全局引用 | ||
DeleteLocalRef | 删除 localRef所指向的局部引用 | ||
AllocObject | 分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用。clazz 参数务必不要引用数组类。 | ||
getObjectClass | 返回对象的类 | ||
IsSameObject | 测试两个引用是否引用同一 Java 对象 | ||
NewString | 利用 Unicode 字符数组构造新的 java.lang.String 对象 | ||
GetStringLength | 返回 Java 字符串的长度(Unicode 字符数) | ||
GetStringChars | 返回指向字符串的 Unicode 字符数组的指针。该指针在调用 ReleaseStringchars() 前一直有效 | ||
ReleaseStringChars | 通知虚拟机平台相关代码无需再访问 chars。参数chars 是一个指针,可通过 GetStringChars() 从 string 获得 | ||
NewStringUTF | 利用 UTF-8 字符数组构造新 java.lang.String 对象 | ||
GetStringUTFLength | 以字节为单位返回字符串的 UTF-8 长度 | ||
GetStringUTFChars | 返回指向字符串的 UTF-8 字符数组的指针。该数组在被ReleaseStringUTFChars() 释放前将一直有效 | ||
ReleaseStringUTFChars | 通知虚拟机平台相关代码无需再访问 utf。utf 参数是一个指针,可利用 GetStringUTFChars() 获得 | ||
NewObjectArray | 构造新的数组,它将保存类 elementClass 中的对象。所有元素初始值均设为 initialElement | ||
Set<PrimitiveType>ArrayRegion |
将基本类型数组的某一区域从缓冲区中复制回来的一组函数 | ||
GetFieldID | 返回类的实例(非静态)域的属性 ID。该域由其名称及签名指定。访问器函数的Get<type>Field 及 Set<type>Field 系列使用域 ID 检索对象域。GetFieldID() 不能用于获取数组的长度域。应使用GetArrayLength()。 |
||
Get<type>Field |
该访问器例程系列返回对象的实例(非静态)域的值。要访问的域由通过调用GetFieldID() 而得到的域 ID 指定。 | ||
Set<type>Field |
该访问器例程系列设置对象的实例(非静态)属性的值。要访问的属性由通过调用SetFieldID() 而得到的属性 ID指定。 | ||
GetStaticFieldID GetStatic<type>Field SetStatic<type>Field |
同上,只不过是静态属性。 | ||
GetMethodID | 返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。 GetMethodID() 可使未初始化的类初始化。要获得构造函数的方法 ID,应将 |
||
CallVoidMethod | |||
CallObjectMethod | |||
CallBooleanMethod | |||
CallByteMethod | |||
CallCharMethod | |||
CallShortMethod | |||
CallIntMethod | |||
CallLongMethod | |||
CallFloatMethod | |||
CallDoubleMethod | |||
GetStaticMethodID | 调用静态方法 | ||
Call<type>Method |
|||
RegisterNatives | 向 clazz 参数指定的类注册本地方法。methods 参数将指定 JNINativeMethod 结构的数组,其中包含本地方法的名称、签名和函数指针。nMethods 参数将指定数组中的本地方法数。 | ||
UnregisterNatives | 取消注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。该函数不应在常规平台相关代码中使用。相反,它可以为某些程序提供一种重新加载和重新链接本地库的途径。 |
域描述符
域 | Java 语言 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
- 引用类型则为 L + 该类型类描述符。
- 数组,其为 : [ + 其类型的域描述符。
- 多维数组则是 n个[ +该类型的域描述符, N代表的是几维数组。
String类型的域描述符为 Ljava/lang/String;
[ + 其类型的域描述符 + ;
int[] 其描述符为[I
float[] 其描述符为[F
String[] 其描述符为[Ljava/lang/String;
Object[]类型的域描述符为[Ljava/lang/Object;
int [][] 其描述符为[[I
float[][] 其描述符为[[F
将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符,规则如下: (参数的域描述符的叠加)返回类型描述符。对于没有返回值的,用V(表示void型)表示。Java层方法 JNI函数签名
String test () Ljava/lang/String;
int f (int i, Object object) (ILjava/lang/Object;)I
void set (byte[] bytes) ([B)V
JNIEnv与JavaVM
JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
JNIEnv 与 JavaVM : 注意区分这两个概念;
- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
- JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
JNIEnv 体系结构
线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv。
JNIEnv 不能跨线程 :
- 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;
- 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此可以接受不同的 JNIEnv;
JNIEnv 结构 :
由上面的代码可以得出, JNIEnv 是一个指针, 指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组, 这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;
UTF-8编码
JNI使用改进的UTF-8字符串来表示不同的字符类型。Java使用UTF-16编码。UTF-8编码主要使用于C语言,因为它的编码用\u000表示为0xc0,而不是通常的0×00。非空ASCII字符改进后的字符串编码中可以用一个字节表示。
异常
JNI允许用户使用Java异常处理。大部分JNI方法会返回错误代码但本身并不会报出异常。因此,很有必要在代码本身进行处理,将异常抛给Java。在JNI内部,首先会检查调用函数返回的错误代码,之后会调用ExpectOccurred()返回一个错误对象。
JNI函数实战
so的入口函数
JNI_OnLoad()
与JNI_OnUnload()
当VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。
- 告诉VM此C组件使用那一个JNI版本。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
- 由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 。
返回值
返回字符串
|
返回数组
|
返回自定义对象
|
返回自定义对象的数组
|
这里的createItem
是对返回自定义对象功能的一个封装函数:jobject createItem(JNIEnv *env, jint d1, jstring d2) {
jclass pJclass = env->FindClass("org/hy/modules/test/entity/TestInfo");
jmethodID method_init = env->GetMethodID(pJclass, "<init>", "()V");
jfieldID nameField = env->GetFieldID(pJclass, "name", "Ljava/lang/String;");
jfieldID dataField = env->GetFieldID(pJclass, "data", "Ljava/lang/Integer;");
jobject obj = env->NewObject(pJclass, method_init);
env->SetObjectField(obj, nameField, env->NewStringUTF("Data"));
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID integerMethod = env->GetMethodID(integerClass, "<init>", "(I)V");
jobject integerObj = env->NewObject(integerClass, integerMethod, d1);
env->SetObjectField(obj, dataField, integerObj);
return obj;
}
操作Java层的类
|
回调Java层方法
这里分为两种
- 在jni中找到类,创建对象,调用该对象函数
- 将接口为参数给jni函数,jni调用该接口中的函数,完成接口回调功能
首先是第一个:public native void testCallBackToMethod();
jclass pJclass = env->FindClass("org/hy/modules/jni/PrintNative");
jmethodID pJmethodId = env->GetMethodID(pJclass, "<init>", "()V");
jobject pJobject = env->NewObject(pJclass, pJmethodId);
jmethodID testMethodId = env->GetMethodID(pJclass, "test", "(I)I");
jint i = env->CallIntMethod(pJobject, testMethodId, 2);
printf("%d", i);
这里只是简单调用了对象中的函数
下面就是接口回调函数public native void testCallBack(CallBack callBack);
jclass callback_cls = env->GetObjectClass(callback);
jmethodID callback_mth = env->GetMethodID(callback_cls, "callback", "(Ljava/lang/String;)V");
env->CallVoidMethod(callback, callback_mth, env->NewStringUTF("aabbcc"));
可以看到她不需要找类,创建新的对象,直接找到需要调用的函数名称并调用即可。
注意,上面是在主进程中使用,如果在jni中的线程中单独使用,则需要特殊处理
|
jbytearray转c++byte数组
|
jbyteArray 转 c++中的BYTE[]
|
C++中的BYTE[]转jbyteArray
|
jbyteArray 转 char *
|
char* 转jstring
|
jstring 转char*
|