NI的基本类型
在JNI中有一些基本类型,这些基本类型只能在JNI层使用
序号 属性名 java层对应的类型 1 jobject Object 2 jboolean boolean 3 jbyte byte 4 jchar char 5 jshort short 6 jint Object 7 jlong long 8 jfloat float 9 jdouble double 10 void void
由于下面的函数名都会包含这些基本属性,我这这里标记为<type>,代表了以上的类型
java类型的签名
序号 属性名 java层对应的类型 1 Z boolean 2 B byte 3 C char 4 S short 5 I int 6 J long 7 F float 8 D boolean 9 L 类 10 [B type[]
关于变量的操作
序号 方法名 备注 1 jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) 根据变量名以及变量签名获得参数ID 2 jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig) 根据变量名以及变量签名获得静态参数ID 3 <type> Get<type>Field(jobject obj, jfieldID fieldID) 根据fieldID获取变量 4 void Set<type>Field(jobject obj, jfieldID fieldID, <type>value) 根据fieldID设置变量值为value 5 <type> GetStatic<type>Field(jobject obj, jfieldID fieldID) 根据fieldID获取静态变量<type> 6 void SetStatic<type>Field(jobject obj, jfieldID fieldID, <type>value) 根据fieldID设置静态变量值为value
类的操作
序号 方法名 备注 1 jclass FindClass(JNIEnv *env, const char *name); name:类的全名。使用UTF-8编码,其中分隔符使用/表示。return:java类对象。发生错误时返回NULL。 2 jclass GetSuperclass(JNIEnv *env, jclass clazz); clazz:需要查询的类对象。return:clazz的父类类对象。如果clazz是Object类对象或者clazz是接口的类信息,返回NULL。 3 jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1,jclass clazz2); 当我们将一个对象从一种类型转换为另一种类型之前,我们必须确保这种类型转换是安全的。我们可以通过如下方法去判断这两种类型是否可以互相转换。 clazz1:原始类型。class2:目标类型。return:当前类型转换是否安全。 4 jclass DefineClass(JNIEnv *env, const char *name, jobject loader,const jbyte *buf, jsize bufLen); name:类的全名,必须是被UTF-8编码过的字符串。loader:类加载器buf:包含类数据的缓冲区。这个缓冲区包含一个原始类数据,并且要求这个类在调用此方法前没有被JVM所引用。bufLen:缓冲区长度。return:java类对象。发生错误时返回NULL。
创建对象的基本操作
序号 方法名 备注 1 jobject AllocObject(JNIEnv *env, jclass clazz); 返回使用clazz类创建的对象,如果clazz没有默认的构造方法,则返回NULL。 2 jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, …); ...代表着传入构造参数 3 jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args); args:这里需要传入参数数组 4 jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args); args:指向变参列表的指针 5 jclass GetObjectClass(JNIEnv *env, jobject obj); 通过java层传过来的对象创建jObject 6 jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj); 通过如下方法可以得到当前对象的引用类型是全局引用、局部引用还是弱全局引用。
typedef enum jobjectRefType {
JNIInvalidRefType = 0,//无效引用
JNILocalRefType = 1,//局部引用
JNIGlobalRefType = 2,//全局引用
JNIWeakGlobalRefType = 3//弱全局引用
} jobjectRefType;
其他关于对象的一些方法
序号 方法名 备注 1 jboolean IsInstanceOf(JNIEnv *env, jobject obj,jclass clazz); 在Java中我们使用instanceof来判断一个对象是否是一个类的实例,在JNI中,需要通过如下方法来进行实例运算。 2 jboolean IsSameObject(JNIEnv *env, jobject ref1,jobject ref2); 在Java中使用==可以判断两个引用是否指向同一个对象,在JNI中使用下列方法可以进行相同的判断,无论是全局引用,局部引用还是弱全局引用。
关于方法的操作
序号 方法名 备注 1 jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig); 根据方法名以及方法签名获得方法ID 2 jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) 根据方法名以及方法签名获得静态方法ID 3 <type> Call<type>Method(jobject obj, jmethodID methodID, ...) 根据methodID及传入方法变量执行方法 4 <type> Call<type>MethodV(jobject obj, jmethodID methodID, va_list args) 根据methodID及传入方法变量指针执行方法 5 <type> Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue* args) 根据methodID及传入方法变量数组执行方法 6 <type> CallNonvirtual<type>Method(jobject obj, jmethodID methodID, ...) 根据methodID及传入方法变量执行非虚实例方法 7 <type> CallNonvirtual<type>MethodV(jobject obj, jmethodID methodID, va_list args) 根据methodID及传入方法变量指针执行非虚实例方法 8 <type> CallNonvirtual<type>MethodA(jobject obj, jmethodID methodID, const jvalue* args) 根据methodID及传入方法变量数组执行非虚实例方法 9 <type> CallStatic<type>Method(jobject obj, jmethodID methodID, ...) 根据methodID及传入方法变量执行静态方法 10 <type> CallStatic<type>MethodV(jobject obj, jmethodID methodID, va_list args) 根据methodID及传入方法变量指针执行静态方法 11 <type> CallStatic<type>MethodA(jobject obj, jmethodID methodID, const jvalue* args) 根据methodID及传入方法变量数组执行静态方法 12 <type> CallStaticNonvirtual<type>Method(jobject obj, jmethodID methodID, ...) 根据methodID及传入方法变量执行静态非虚实例方法 13 <type> CallStaticNonvirtual<type>MethodV(jobject obj, jmethodID methodID, va_list args) 根据methodID及传入方法变量指针执行静态非虚实例方法 14 <type> CallStaticNonvirtual<type>MethodA(jobject obj, jmethodID methodID, const jvalue* args) 根据methodID及传入方法变量数组执行静态非虚实例方法
关于异常的操作
在这里需要注意的是,jni层在出错的时候不会像java层一样,抛出异常,如果需要抛出java层的异常需要调用到如下的方法
序号 方法名 备注 1 jint Throw(JNIEnv *env, jthrowable obj); obj:一个java.lang.Throwable对象。return:异常抛出结果。0表示异常正常抛出到JVM,否则异常抛出失败。 2 jint ThrowNew(JNIEnv *env, jclass clazz,const char *message); 抛出自定义异常clazz:待抛出的异常类。message:异常消息。要求UTF-8编码。return:异常抛出结果。0表示异常正常抛出到JVM,否则异常抛出失败。 3 jthrowable ExceptionOccurred(JNIEnv *env); 如果我们需要知道我们之前的操作是否存在JVM抛出的异常时,我们可以调用如下方法获取异常对象,注意:即使我们获取到了异常对象,这个异常仍然在JVM中存在,直到我们调用ExceptionClear方法清空异常。return:当前发生的异常对象。如果没有异常抛出则返回NULL。 4 void ExceptionDescribe(JNIEnv *env); 当我们拦截到一个异常,我们可以使用如下方法打印错误栈中的内容。就像Java中的printStackTrace: 5 void ExceptionClear(JNIEnv *env); 调用以下方法可以清空当前产生的全部异常信息 6 void FatalError(JNIEnv *env, const char *msg); 对于上述异常,我们可以产生也可以拦截。但是如果发生了一些错误导致我们的程序无法再正常运行下去了,则可以发送一个错误信息给JVM,此时程序将被终止。msg:错误信息。UTF-8。 7 jboolean ExceptionCheck(JNIEnv *env); 我们可以通过以下方法查看当前有没有异常产生。return:是否存在异常信息。
关于引用类型
在JNI中引用类型分为三种,分别是全局引用,局部引用和弱全局引用。 全局引用可以跨方法(本地方法返回后仍然有效),跨线程使用,直到手动释放才会失效。该引用不会被GC回收。相当于java中的强引用 局部引用是JVM负责的引用类型,其被JVM分配管理,并占用JVM的资源。局部引用在native方法返回后被自动回收。局部引用只在创建它们的线程中有效,不能跨线程传递。 弱全局引用是一种特殊的全局引用。跟普通的全局引用不同的是,一个弱全局引用允许Java对象被垃圾回收器回收。当垃圾回收器运行的时候,如果一个对象仅被弱全局引用所引用,则这个引用将会被回收。一个被回收了的弱引用指向NULL,开发者可以将其与NULL比较来判定该对象是否可用。
序号 方法名 备注 1 jobject NewGlobalRef(JNIEnv *env, jobject obj); 通过以下方法可以将任意引用转换为全局引用。由于全局引用不再受到JVM统一管理,所以我们要在不用时手动删除。obj:任意类型的引用。return:全局引用。如果内存不足返回NULL。 2 void DeleteGlobalRef(JNIEnv *env, jobject globalRef); 通过以下方法可以删除一个全局引用。globalRef:全局引用。 3 jobject NewLocalRef(JNIEnv *env, jobject ref); 通过以下方法可以创建一个局部引用。通常情况下,我们在native方法中创建的引用都是局部引用,并且不需要手动进行释放,当方法返回时,这个引用就会被自动销毁。ref:全局或者局部引用return:局部引用 4 void DeleteLocalRef(JNIEnv *env, jobject localRef); 通过以下方法可以删除一个局部引用。localRef:局部引用。 5 jint EnsureLocalCapacity(JNIEnv *env, jint capacity); 虚拟机将确保每个本地方法至少可以创建16个局部引用。但是在如今的场景中,16个局部引用已经远远不能满足开发需求了。为了为了解决这个问题,JNI提供了查询可用引用容量的方法,我们在创建超出限制的引用时最好先确认是否有足够的空间可以。capacity:给定局部引用的数量。return:JNI_OK表示当前线程栈可以创建capacity个局部引用。返回其他值表示不可用,并抛出一个OutOfMemoryError异常存在异常OutOfMemoryError 6 jint PushLocalFrame(JNIEnv *env, jint capacity); 局部栈帧的入栈capacity:给定局部引用的数量。return:JNI_OK表示当前线程栈可以创建capacity个局部引用。返回其他值表示不可用,并抛出一个OutOfMemoryError异常 7 jobject PopLocalFrame(JNIEnv *env, jobject result); 销毁其中的全部局部引用。result:给定保存栈帧的引用,如果不需要前一个栈帧则可以传入NULL。return:前一个帧的引用。 8 jweak NewWeakGlobalRef(JNIEnv *env, jobject obj); 销毁其中的全部局部引用。result:给定保存栈帧的引用,如果不需要前一个栈帧则可以传入NULL。return:前一个帧的引用。 9 jobject PopLocalFrame(JNIEnv *env, jobject result); 新建弱引用obj:任意对象。return:返回弱全局引用,如果obj为NULL则返回NULL。 10 void DeleteWeakGlobalRef(JNIEnv *env, jweak obj); 删除弱引用obj:弱全局引用。
关于字符串的一些操作
序号 方法名 备注 1 jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len); 新建java字符串。unicodeChars:一个指向Unicode编码的字符数组的指针。len:unicodeChars的长度return:Java字符串对象 2 jsize GetStringLength(JNIEnv *env, jstring string); 获取jstring字符串的长度string:Java字符串对象return:字符串长度 3 const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy); 获取jstring字符串的字符串数组。isCopy:注意,这个参数很重要,这是一个指向Java布尔类型的指针。函数返回之后应当检查这个参数的值,如果值为JNI_TRUE表示返回的字符是Java字符串的拷贝,我们可以对其中的值进行任意修改。如果返回值为JNI_FALSE,表示这个字符指针指向原始Java字符串的内存,这时候对字符数组的任何修改都将会原始字符串的内容。如果你不关心字符数组的来源,或者说你的操作不会对字符数组进行任何修改,可以传入NULL。return:指向字节数组的指针 4 void ReleaseStringChars(JNIEnv *env, jstring string,const jchar *chars); 释放从Java字符串中获取的字符数组string:Java字符串对象。chars:字符数组。 5 jstring NewStringUTF(JNIEnv *env, const char *bytes); 新建UTF-8编码字符串。bytes:UTF-8编码的字节数组。return:UTF-8编码的Java字符串对象 6 jsize GetStringUTFLength(JNIEnv *env, jstring string); 获取UTF-8字符串的长度 7 const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy); 获取UTF-8编码的Java字符串的 8 void ReleaseStringUTFChars(JNIEnv *env, jstring string,const char *utf); 释放从UTF-8字符串中获取的字符数组 9 void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); 从Java字符串中截取一段字符。str:Java字符串对象。start:起始位置。len:截取长度。buf:保存截取结果的缓冲区。 10 void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf); 从UTF-8字符串中截取一段字符 11 const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy); 直接获取字符串指针 12 void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray); 直接获取字符串指针
关于java数组的一些操作
序号 方法名 备注 1 jsize GetArrayLength(JNIEnv *env, jarray array); 获取数组长度 2 jobjectArray NewObjectArray(JNIEnv *env, jsize length,jclass elementClass, jobject initialElement); 新建对象数组ength:数组的长度。elementClass:数组中的对象类型。initialElement:数组中的每个元素都会使用这个值进行初始化,可以为NULL。return:对象数组,创建失败返回NULL 3 jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index); 获取对象数组元素。array:对象数组,index:位置索引 4 void SetObjectArrayElement(JNIEnv *env, jobjectArray array,jsize index, jobject value); 设置对象数组元素 5 j<type>Array New<type>Array(jsize length) 新建对象数组元素 6 <type>* Get<type>ArrayElements(jbooleanArray array, jboolean* isCopy) 获取基本数据类型数组元素的函数原型 7 void Release<type>ArrayElements(<type>Array array, jboolean* elems,jint mode) 释放基本数据类型数组。mode 0 :copy back the content and free the elems buffer,mode JNI_COMMIT:copy back the content but do not free the elems buffer,mode JNI_ABORT:free the buffer without copying back the possible changes 8 (Get<type>ArrayRegion)(JNIEnv , <type>Array,jsize, jsize, jboolean*); 截取数组 9 void Set<type>ArrayRegion(JNIEnv *env, ArrayType array,jsize start, jsize len, const NativeType *buf); 设置数组范围 10 void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy); 操作数组指针 11 void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode); 操作数组指针
本地方法
当我们在一个Java文件中书写一个native的方法的时候,为了让JNI识别我们的方法,就需要采用注册的方式。
序号 方法名 备注 1 jint RegisterNatives(JNIEnv *env, jclass clazz,const JNINativeMethod *methods, jint nMethods); 注册本地方法。clazz: 包含本地方法的Java类。methods: 本地方法描述数组。nMethods: 数组长度。return:成功返回0,否则注册失败 2 jint UnregisterNatives(JNIEnv *env, jclass clazz); return:返回0表示成功,否则为失败。解除本地方法,当我们确定不再需要本地方法的时候,可以调用这个方法来解除本地方法的注册。这个方法会导致本地库的重新加载和链接。
作者:月影路西法 链接:https://www.jianshu.com/p/4334f084b2a4 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。