Android NDK- JNI 基础

Posted teletian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android NDK- JNI 基础相关的知识,希望对你有一定的参考价值。

JNI 类型

JNI 中有许多和 Java 相对应的类型

Java 类型JNI 类型
booleanjboolean
bytejbyte
charjchar
shortjshort
intjint
longjlong
floatjfloat
doublejdouble
voidvoid
java.lang.Classjclass
java.lang.Stringjstring
java.lang.Throwablejthrowable
objectjobject
object[]jobjectarray
boolean[]jbooleanarray
byte[]jbytearray
char[]jchararray
short[]jshortarray
int[]jintarray
long[]jlongarray
float[]jfloatarray
double[]jdoublearray

类型签名

Java 类型JNI 类型签名
voidV
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
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 的引用有两层含义:

  1. Java 中的引用类型
  2. C/C++ 中的指针

但是如果引用被 JVM 释放了,指针仍然指向一个地址,只是对应的地址中数据已经被释放了

JNI 的引用分为四种:

  1. 全局引用(GlobalReferences):全局有效。JVM 无法释放回收,必须通过调用 DeleteGlobalRef() 显式释放。
    创建全局引用:jobject NewGlobalRef(JNIEnv *env, jobject obj);
    释放全局引用:void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

  2. 弱全局引用(WeakGlobalReferences):一种特殊的全局引用,可以被 JVM 回收。
    创建弱全局引用:jobject NewWeakGlobalRef(JNIEnv *env, jobject obj);
    释放弱全局引用:void DeleteWeakGlobalRef(JNIEnv *env, jobject globalRef);

  3. 局部引用(LocalReferences):在方法内创建,方法结束后自动释放。虽然会在方法结束后自动释放,但是如果消耗过多 JVM 资源,也可以手动释放。
    创建局部引用:jobject NewLocalRef(JNIEnv *env, jobject obj);
    释放局部引用:void DeleteLocalRef(JNIEnv *env, jobject globalRef);

    虽然方法结束会自动释放,但是建议使用完了就手动释放。尤其以下两种情况必须手动释放:

    1. 引用一个很大的 Java 对象
    2. 在 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 方法去释放本地内存

  4. 无效引用(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编程技术基础介绍

Android Studio NDK基础使用

Android Studio 3.X NDK 开发基础

JNI和NDK

Android 使用 jni Demo示例

Android 使用 jni Demo示例