Android NDK- JNI 基础
Posted teletian
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android NDK- JNI 基础相关的知识,希望对你有一定的参考价值。
JNI 类型
JNI 中有许多和 Java 相对应的类型
Java 类型 | JNI 类型 |
---|---|
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
void | void |
java.lang.Class | jclass |
java.lang.String | jstring |
java.lang.Throwable | jthrowable |
object | jobject |
object[] | jobjectarray |
boolean[] | jbooleanarray |
byte[] | jbytearray |
char[] | jchararray |
short[] | jshortarray |
int[] | jintarray |
long[] | jlongarray |
float[] | jfloatarray |
double[] | jdoublearray |
类型签名
Java 类型 | JNI 类型签名 |
---|---|
void | V |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
L fully-qualified-class ; | fully-qualified-class |
type[] | [ type |
method type | ( arg-types ) ret-type |
例如,下面的 Java 方法:
public long foo(int n, String s, int[] arr)
在 JNI 中的签名如下:
(ILjava/lang/String;[I)J
JNI 引用
JNI 定义了八种 Java 基本类型,其余的 jobject、jclass、jarray、jxxxArray、jstring 等都是引用类型。
JNI 的引用有两层含义:
- Java 中的引用类型
- C/C++ 中的指针
但是如果引用被 JVM 释放了,指针仍然指向一个地址,只是对应的地址中数据已经被释放了
JNI 的引用分为四种:
-
全局引用(GlobalReferences):全局有效。JVM 无法释放回收,必须通过调用 DeleteGlobalRef() 显式释放。
创建全局引用:jobject NewGlobalRef(JNIEnv *env, jobject obj);
释放全局引用:void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
-
弱全局引用(WeakGlobalReferences):一种特殊的全局引用,可以被 JVM 回收。
创建弱全局引用:jobject NewWeakGlobalRef(JNIEnv *env, jobject obj);
释放弱全局引用:void DeleteWeakGlobalRef(JNIEnv *env, jobject globalRef);
-
局部引用(LocalReferences):在方法内创建,方法结束后自动释放。虽然会在方法结束后自动释放,但是如果消耗过多 JVM 资源,也可以手动释放。
创建局部引用:jobject NewLocalRef(JNIEnv *env, jobject obj);
释放局部引用:void DeleteLocalRef(JNIEnv *env, jobject globalRef);
虽然方法结束会自动释放,但是建议使用完了就手动释放。尤其以下两种情况必须手动释放:
- 引用一个很大的 Java 对象
- 在 for 循环中创建了大量的引用。引用多了之后会报 ReferenceTable overflow 异常。
哪些场景需要释放?JNI 函数内部创建的 jobject、jclass、jstring、jarray 等引用都需要释放。
- FindClass / DeleteLocalRef
- NewString / DeleteLocalRef
- NewStringUTF / DeleteLocalRef
- NewObject / DeleteLocalRef
- NewXxxArray / DeleteLocalRef
- GetObjectField / DeleteLocalRef
- GetObjectClass / DeleteLocalRef
- GetObjectArrayElement / DeleteLocalRef
- 注意:对于 GetStringChars、GetStringUTFChars、GetXxxArrayElements 基本类型数组,需要调用对应的 Release 方法去释放本地内存
-
无效引用(InvalidReferences):无效引用一般情况下没有什么用,不展开介绍。
字段和方法 ID
jfieldID 和 jmethodID 是常规的 C 指针类型,它们的声明如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */
GetFieldID / GetXxxField / SetXxxField
GetStaticFieldID / GetStaticXxxField / SetStaticXxxField
/*
* @param env: JN I接口指针。
* @param clazz:一个 Java 类。
* @param name:字段名称,以 \\0 结尾的 UTF-8 字符串。
* @param sig:字段签名,以 \\0 结尾的 UTF-8 字符串。
* @return 返回字段 ID,如果操作失败返回 NULL。
*/
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
// 获取静态字段
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jobject GetObjectField(JNIEnv*, jobject, jfieldID);
jboolean GetBooleanField(JNIEnv*, jobject, jfieldID);
jbyte GetByteField(JNIEnv*, jobject, jfieldID);
jchar GetCharField(JNIEnv*, jobject, jfieldID);
jshort GetShortField(JNIEnv*, jobject, jfieldID);
jint GetIntField(JNIEnv*, jobject, jfieldID);
jlong GetLongField(JNIEnv*, jobject, jfieldID);
jfloat GetFloatField(JNIEnv*, jobject, jfieldID);
jdouble GetDoubleField(JNIEnv*, jobject, jfieldID);
// 获取静态字段值
jobject GetStaticObjectField(JNIEnv*, jclass, jfieldID);
jboolean GetStaticBooleanField(JNIEnv*, jclass, jfieldID);
jbyte GetStaticByteField(JNIEnv*, jclass, jfieldID);
jchar GetStaticCharField(JNIEnv*, jclass, jfieldID);
jshort GetStaticShortField(JNIEnv*, jclass, jfieldID);
jint GetStaticIntField(JNIEnv*, jclass, jfieldID);
jlong GetStaticLongField(JNIEnv*, jclass, jfieldID);
jfloat GetStaticFloatField(JNIEnv*, jclass, jfieldID);
jdouble GetStaticDoubleField(JNIEnv*, jclass, jfieldID);
void SetObjectField(JNIEnv*, jobject, jfieldID, jobject);
void SetBooleanField(JNIEnv*, jobject, jfieldID, jboolean);
void SetByteField(JNIEnv*, jobject, jfieldID, jbyte);
void SetCharField(JNIEnv*, jobject, jfieldID, jchar);
void SetShortField(JNIEnv*, jobject, jfieldID, jshort);
void SetIntField(JNIEnv*, jobject, jfieldID, jint);
void SetLongField(JNIEnv*, jobject, jfieldID, jlong);
void SetFloatField(JNIEnv*, jobject, jfieldID, jfloat);
void SetDoubleField(JNIEnv*, jobject, jfieldID, jdouble);
// 设置静态字段值
void SetStaticObjectField(JNIEnv*, jclass, jfieldID, jobject);
void SetStaticBooleanField(JNIEnv*, jclass, jfieldID, jboolean);
void SetStaticByteField(JNIEnv*, jclass, jfieldID, jbyte);
void SetStaticCharField(JNIEnv*, jclass, jfieldID, jchar);
void SetStaticShortField(JNIEnv*, jclass, jfieldID, jshort);
void SetStaticIntField(JNIEnv*, jclass, jfieldID, jint);
void SetStaticLongField(JNIEnv*, jclass, jfieldID, jlong);
void SetStaticFloatField(JNIEnv*, jclass, jfieldID, jfloat);
void SetStaticDoubleField(JNIEnv*, jclass, jfieldID, jdouble);
GetMethodID / CallXxxMethod
GetStaticMethodID / CallStaticXxxMethod
/*
* @param env: JNI 接口指针。
* @param clazz:一个 Java 类。
* @param name:方法名称,以 \\0 结尾的 UTF-8 字符串。
* @param sig:方法签名,以 \\0 结尾的 UTF-8 字符串。
* @return 返回方法 ID,如果操作失败返回 NULL。
*/
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
// 获取静态方法
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
/*
* 调用实例方法
*
* @param env: JNI 接口指针。
* @param jobject: 一个 Java 对象。
* @param methodID:java 函数的 methodID, 必须通过调用 GetMethodID() 来获得。
* @param ...:java 函数的参数。
* @param args:java 函数的参数数组。
* @param args:java 函数参数的 va_list。
* @return 返回 Java 对象,无法构造该对象则返回 NULL。
*/
jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...);
jobject CallObjectMethodV(JNIEnv*, jobject, jmethodID, va_list);
jobject CallObjectMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jboolean CallBooleanMethod(JNIEnv*, jobject, jmethodID, ...);
jboolean CallBooleanMethodV(JNIEnv*, jobject, jmethodID, va_list);
jboolean CallBooleanMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jbyte CallByteMethod(JNIEnv*, jobject, jmethodID, ...);
jbyte CallByteMethodV(JNIEnv*, jobject, jmethodID, va_list);
jbyte CallByteMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jchar CallCharMethod(JNIEnv*, jobject, jmethodID, ...);
jchar CallCharMethodV(JNIEnv*, jobject, jmethodID, va_list);
jchar CallCharMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jshort CallShortMethod(JNIEnv*, jobject, jmethodID, ...);
jshort CallShortMethodV(JNIEnv*, jobject, jmethodID, va_list);
jshort CallShortMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jint CallIntMethod(JNIEnv*, jobject, jmethodID, ...);
jint CallIntMethodV(JNIEnv*, jobject, jmethodID, va_list);
jint CallIntMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jlong CallLongMethod(JNIEnv*, jobject, jmethodID, ...);
jlong CallLongMethodV(JNIEnv*, jobject, jmethodID, va_list);
jlong CallLongMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jfloat CallFloatMethod(JNIEnv*, jobject, jmethodID, ...);
jfloat CallFloatMethodV(JNIEnv*, jobject, jmethodID, va_list);
jfloat CallFloatMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jdouble CallDoubleMethod(JNIEnv*, jobject, jmethodID, ...);
jdouble CallDoubleMethodV(JNIEnv*, jobject, jmethodID, va_list);
jdouble CallDoubleMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
void CallVoidMethod(JNIEnv*, jobject, jmethodID, ...);
void CallVoidMethodV(JNIEnv*, jobject, jmethodID, va_list);
void CallVoidMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
// 调用静态方法
jobject CallStaticObjectMethod(JNIEnv*, jclass, jmethodID, ...);
jobject CallStaticObjectMethodV(JNIEnv*, jclass, jmethodID, va_list);
jobject CallStaticObjectMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jboolean CallStaticBooleanMethod(JNIEnv*, jclass, jmethodID, ...);
jboolean CallStaticBooleanMethodV(JNIEnv*, jclass, jmethodID, va_list);
jboolean CallStaticBooleanMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jbyte CallStaticByteMethod(JNIEnv*, jclass, jmethodID, ...);
jbyte CallStaticByteMethodV(JNIEnv*, jclass, jmethodID, va_list);
jbyte CallStaticByteMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jchar CallStaticCharMethod(JNIEnv*, jclass, jmethodID, ...);
jchar CallStaticCharMethodV(JNIEnv*, jclass, jmethodID, va_list);
jchar CallStaticCharMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jshort CallStaticShortMethod(JNIEnv*, jclass, jmethodID, ...);
jshort CallStaticShortMethodV(JNIEnv*, jclass, jmethodID, va_list);
jshort CallStaticShortMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jint CallStaticIntMethod(JNIEnv*, jclass, jmethodID, ...);
jint CallStaticIntMethodV(JNIEnv*, jclass, jmethodID, va_list);
jint CallStaticIntMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jlong CallStaticLongMethod(JNIEnv*, jclass, jmethodID, ...);
jlong CallStaticLongMethodV(JNIEnv*, jclass, jmethodID, va_list);
jlong CallStaticLongMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jfloat CallStaticFloatMethod(JNIEnv*, jclass, jmethodID, ...);
jfloat CallStaticFloatMethodV(JNIEnv*, jclass, jmethodID, va_list);
jfloat CallStaticFloatMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jdouble CallStaticDoubleMethod(JNIEnv*, jclass, jmethodID, ...);
jdouble CallStaticDoubleMethodV(JNIEnv*, jclass, jmethodID, va_list);
jdouble CallStaticDoubleMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
void CallStaticVoidMethod(JNIEnv*, jclass, jmethodID, ...);
void CallStaticVoidMethodV(JNIEnv*, jclass, jmethodID, va_list);
void CallStaticVoidMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
在 JNI 中调用 java 对象的变量或者方法时常常会用到 jfieldID 和 jmethodID。
可以看下面的例子:
JNIEXPORT void JNICALL Java_com_sample_MainActivity_stringFromJNI(
JNIEnv* env, jobject this_obj)
/* get the class */
jclass class_obj = (*env)->GetObjectClass(env, this_obj);
/* get the field ID */
jfieldID id_age = (*env)->GetFieldID(env, class_obj, "age", "I");
jfieldID id_name = (*env)->GetFieldID(env, class_obj, "name", "Ljava/lang/String;");
/* get the field value */
jint age = (*env)->GetIntField(env, this_obj, id_age);
jstring age = (*env)->GetIntField(env, this_obj, id_age);
age += 1;
/* set the field value */
(*env)->SetIntField(env, this_obj, id_age, age);
jmethodID methodInActivity =
env->GetMethodID(env->GetObjectClass(this_obj), "methodInActivity", "()V");
env->CallVoidMethod(this_obj, methodInActivity);
JNI 类和对象
JNI 类
/*
* @brief 定义新的类或接口
*
* @param env: JNI 接口指针.
* @param name: 要定义的类或接口的名称。
* @param loader: 分配给已定义类的类加载器。
* @param buf: 包含 .class 文件数据的缓冲区。
* @param bufLen: 缓冲区长度。
* @return 返回 Java 类。如果发生错误,则返回 NULL。
*/
jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen);
/*
* @brief 加载一个已经定义过的类
*
* @param env: JNI 接口指针。
* @param name: 完全限定的类名。例如 java.lang.String:java/lang/String。
* 如果名称以"["(数组签名字符)开头,则返回数组类。
* @return 返回 Java 类。如果发生错误,则返回 NULL。
*/
jclass FindClass(JNIEnv *env, const char *name);
/*
* @brief: 加载一个已经定义过的类的父类
*
* @param env: JNI 接口指针。
* @param clazz: 一个 Java 类。
* @return 返回父类。如果发生错误,则返回 NULL。
*/
jclass GetSuperclass(JNIEnv *env, jclass clazz);
/*
* @brief 判断 clazz1 是否可以安全得转换为 clazz2
*
* @param env: JNI 接口指针。
* @param clazz1: 第一个类参数。
* @param clazz2: 第二个类参数。
* @return 如果可以转换,则返回 JNI_TRUE
*/
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
JNI 对象
/*
* @brief 创建新的 Java 对象,而无需调用该对象的任何构造函数。返回该对象的引用。clazz 参数不能为任何数组类。
*
* @param env: JNI接口指针。
* @param clazz: 一个 Java 类。
* @return 返回 Java 对象,无法构造该对象则返回 NULL。
*/
jobject AllocObject(JNIEnv *env, jclass clazz);
/*
* @brief 创建新的 Java 对象,指定够照方法
*
* @param env: JNI 接口指针。
* @param clazz: 一个 Java 类。
* @param methodID:构造函数的 methodID。必须通过 GetMethodID() 获取构造方法的 methodID。
构造方法名为 <init>,方法签名为 (I)V、()V 等
* @param ...:构造函数的参数。
* @param args:构造函数的参数数组。
* @param args:构造函数参数的 va_list。
* @return 返回 Java 对象,无法构造该对象则返回 NULL。
*/
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
/*
* @brief 获取对象所属的类
*
* @param env: JNI 接口指针。
* @param obj: 一个 Java 对象(必须不是 NULL)。
* @return Java 类。
*/
jclass GetObjectClass(JNIEnv *env, jobject obj);
/*
* @brief 获取 obj 的引用类型。
*
* @param env: JNI 接口指针。
* @param obj: 局部引用、全局引用或者弱全局引用。
* @return 返回以下枚举值之一:
* - 如果 obj 不是有效的引用,则返回 JNIInvalidRefType = 0。
* - 如果 obj 是局部引用类型,则返回 JNILocalRefType = 1。
* - 如果 obj 是全局引用类型,则返回 JNIGlobalRefType = 2。
* - 如果 obj 是弱全局引用类型,则返回 JNIWeakGlobalRefType = 3。
*/
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);
/*
* @brief 判断对象是否是某个类的实例。
*
* @param env:JNI 接口指针。
* @param obj:一个 Java 对象
* @return JNI_TRUE 或者 JNI_FALSE。
*/
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
/*
* @brief 判断两个引用是否引用相同的 Java 对象。
*
* @param env: JNI 接口指针。
* @param ref1:一个Java对象。
* @param ref2:一个Java对象。
* @return 如果 ref1 和 ref2 引用相同的 Java 对象,或者两者均为 NULL,返回 JNI_TRUE; 否则返回 JNI_FALSE。
*/
jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);
JNI 类和对象例子
创建 Java 对象
jclass clazz = env->FindClass("java/lang/Integer");
if (clazz != nullptr)
jmethodID constructMethodId = env->GetMethodID(clazz, "<init>", "(I)V");
jobject integerObject = env->NewObject(clazz, constructMethodId, jvalue);
获取成员变量
extern "C" JNIEXPORT void JNICALL
Java_com_teletian_sample_myndk_MainActivity_testObjectAndroid高级NDK/JNI编程技术基础介绍