一、说明
这里介绍的函数大多是 NDK 开发中常用的函数,但并不是全部,内容稍多,基本可以满足我们的开发需求了,建议通过目录索引来找需要了解的。
这里的函数都是 JNIEnv 操作的相关函数,JNI_OnLoad 等 JavaVM 的方法不在这里介绍。
JNI 有 C、C++ 两种代码风格,即:

1
2
C风格:(*env)->NewStringUTF(env, "Hellow World!");
C++风格:env->NewStringUTF("Hellow World!");

这里我们使用 C++ 风格作为示例。

二、获取版本

  1. jint GetVersion()
    说明:获取当前 JNI 的版本号
    返回值:
1
2
3
4
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006

三、类操作

  1. jclass FindClass(const char* name)
    说明:根据类的全路径找到相应的 jclass 对象
    参数:
  • name:类的全路径,例如 “Ljava/lang/String;”
    示例:
1
jclass mStringClass = env->FindClass("Ljava/lang/String;");
  1. jclass GetSuperclass(jclass clazz)
    说明:返回一个类的父类,如果 clazz 是 Object 类,没有父类,那么将返回 NULL
    参数:
  • jclazz:当前类对象
    示例:
1
jclass clazz = env->GetSuperclass(mStringClass); // clazz is Ljava/lang/Object;
  1. jboolean IsAssignableFrom(jclass clazz1, jclass clazz2)
    说明:判断类1是否可以安全的强制转换类2
    参数:
  • clazz1:类1
  • clazz2:类2

四、对象操作

  1. jobject AllocObject(jclass clazz)
    说明:不调用构造方法创建实例
    参数:
  • clazz:指定对象的类
  1. jobject NewObject(jclass clazz, jmethodID methodID, …)
  2. jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue* args)
  3. jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
    说明:使用指定的构造方法创建类的实例,唯一不同的是输入参数的传入形式不同
    参数:
  • clazz:指定对象的类

  • methodID:指定的构造方法

  • args:输入参数列表

    示例:

    1
    2
    3
    jclass rect_clazz = env->FindClass("android/graphics/Rect");
    jmethodID rect_constructor = env->GetMethodID(rect_clazz, "<init>", "()V");
    jobject rect = env->NewObject(rect_clazz, rect_constructor);
  1. jclass GetObjectClass(jobject obj)
    说明:根据对象获取所属类
    参数:
  • obj:某个 Java 对象实例,不能为 NULL
  1. jobjectRefType GetObjectRefType(jobject obj)
    说明:获取到对象的引用类型,JNI 1.6 新增的方法

参数:

  • obj:某个 Java 对象实例
    返回:

    JNIInvalidRefType = 0 // 该 obj 是个无效的引用
    JNILocalRefType = 1 // 该 obj 是个局部引用
    JNIGlobalRefType = 2 // 该 obj 是个全局引用
    JNIWeakGlobalRefType = 3 // 该 obj 是个全局的弱引用

  1. jboolean IsInstanceOf(jobject obj, jclass clazz)
    说明:判断某个对象是否是指定类的实例
    参数:
  • obj:某个 Java 对象实例
  • clazz:指定的类对象
  1. jboolean IsSameObject(jobject ref1, jobject ref2)
    说明:判断两个对象的引用是否指向的是相同的 Java 对象
    参数:
  • ref1:某个 Java 对象的引用1
  • ref2:某个 Java 对象的引用2

五、域操作

  1. jfieldID GetFieldID(jclass clazz, const char name, const char sig)
    说明:获取类中某个非静态成员变量的ID(域ID)
    参数:
  • clazz:指定对象的类

  • name:这个域(Field)在 Java 类中定义的名字

  • sig:这个域(Field)的类型描述符
    示例:

    1
    2
    3
    4
    5
    jclass clazz = env->FindClass("android/graphics/Rect");
    jfieldID left_field = env->GetFieldID(clazz, "left", "I");
    jfieldID top_field = env->GetFieldID(clazz, "top", "I");
    jfieldID right_field = env->GetFieldID(clazz, "right", "I");
    jfieldID bottom_field = env->GetFieldID(clazz, "bottom", "I");
  1. NativeType Get<type>Field(jobject obj, jfieldID fieldID)
    说明:获取实例域的变量值,这里 type 表示的是一系列方法,如下:
GetField Routine Name Native Type
GetObjectField() jobject
GetBooleanField() jboolean
GetByteField() jbyte
GetCharField() jchar
GetShortField() jshort
GetIntField() jint
GetLongField() jlong
GetFloatField() jfloat
GetDoubleField() jdouble

参数:

  • obj:某个 Java 对象实例

  • fieldID:这个变量的域ID
    示例:

    1
    2
    3
    4
    jobject rect; // 初始化过程省略
    jclass clazz = env->FindClass("android/graphics/Rect");
    jfieldID left_field = env->GetFieldID(clazz, "left", "I");
    jint left = env->GetIntField(rect, left_field);
  1. void Set<type>Field(jobject obj, jfieldID fieldID, NativeType value)
    说明:修改实例域的变量值,这里 type 对应上面的 Get 方法,不再累述
    参数:
  • obj:需要修改的 Java 对象实例

  • fieldID:这个变量的域ID

  • value:需要设置的值
    示例:

    1
    2
    3
    4
    jobject rect; // 初始化过程省略
    jclass clazz = env->FindClass("android/graphics/Rect");
    jfieldID left_field = env->GetFieldID(clazz, "left", "I");
    env->SetIntField(rect, left_field, 1);
  1. jfieldID GetStaticFieldID(jclass clazz, const char name, const char sig)
    说明:同 GetFieldID,只不过这里操作的是静态的域(Filed)

  2. NativeType GetStaticField(jobject obj, jfieldID fieldID)
    说明:同 GetField,只不过这里操作的是静态的域(Filed)

  3. void SetStaticField(jobject obj, jfieldID fieldID, NativeType value)
    说明:同 SetField,只不过这里操作的是静态的域(Filed)

六、方法操作

  1. jmethodID GetMethodID(jclass clazz, const char name, const char sig)
    说明:获取类中某个非静态方法的ID

参数:

  • clazz:指定对象的类
  • name:这个方法在 Java 类中定义的名称,构造方法为 ““
  • sig:这个方法的类型描述符,例如 “()V”,其中括号内是方法的参数,括号后是返回值类型
    示例:

Java 的类定义如下:

1
2
3
4
5
6
7
8
9
10
package com.afei.jnidemo;

class Test {
public Test(){}
public int show(String msg, int number) {
System.out.println("msg: " + msg);
System.out.println("number: " + number);
return 0;
}
}

JNI 调用如下:

1
2
3
jclass clazz = env->FindClass("com/afei/jnidemo/Test");
jmethodID constructor_method = env->GetMethodID(clazz, "<init>", "()V");
jmethodID show_method = env->GetMethodID(clazz, "show", "(Ljava/lang/String;I)I");

​ 签名时其中括号内是方法的参数,括号后是返回值类型。例如 show 方法,第一个参数是 String 类,对应 Ljava/lang/String;(注意后面有一个分号),第二个参数是 int 基本类型,对应的类型描述符是 I,返回值也是 int,同样是 I,所以最终该方法的签名为 “(Ljava/lang/String;I)I”。

  1. NativeType CallMethod(jobject obj, jmethodID methodID, …)
  2. NativeType CallMethodA(jobject obj, jmethodID methodID, jvalue* args)
  3. NativeType CallMethodV(jobject obj, jmethodID methodID, va_list args)
    说明:调用对象的某个方法,唯一不同的是输入参数的传入形式不同,这里 type 表示的是一系列方法,如下:

image-20231123153835482

参数:

  • obj:某个 Java 对象实例

  • methodID:指定方法的ID

  • args:输入参数列表
    示例:

    1
    2
    3
    jclass clazz = env->FindClass("com/afei/jnidemo/Test");
    jmethodID show_method = env->GetMethodID(clazz, "show", "(Ljava/lang/String;I)I");
    jint result = env->CallIntMethod(clazz, show_method, "Hello JNI!", 0);
  1. jmethodID GetStaticMethodID(jclass clazz, const char name, const char sig)
    说明:同 GetMethodID,只不过操作的是静态方法

  2. NativeType CallStaticMethod(jclass clazz, jmethodID methodID, …)

  3. NativeType CallStaticMethodA(jclass clazz, jmethodID methodID, jvalue* args)

  4. NativeType CallStaticMethodV(jclass clazz, jmethodID methodID, va_list args)
    说明:同 NativeType CallMethod,只不过操作的是静态方法,参数也由 jobject 变成了 jclass。

七、全局引用和局部引用

  1. jobject NewGlobalRef(jobject obj)
    说明:创建一个全局引用,不用时必须调用 DeleteGlobalRef() 方法释放。
    参数:
  • obj:某个 Java 对象实例,可以是局部引用或全局引用
    示例:

    1
    2
    3
    4
    5
    jclass mPointFClass;  // global reference to PointF class

    ...
    jclass clazz = env->FindClass("android/graphics/PointF");
    mPointFClass = (jclass) env->NewGlobalRef(clazz);
  1. void DeleteGlobalRef(jobject globalRef)
    说明:释放某个全局引用
    参数:
  • globalRef:某全局引用
  1. jobject NewLocalRef(jobject ref)
    说明:创建一个局部引用。这个方法一般很少用。
    参数:
  • ref:某个引用,可以是全局引用或者局部引用
  1. void DeleteLocalRef(jobject localRef)
    说明:释放某个局部引用
    参数:
  • localRef:某局部引用

    注意:
    局部引用在方法执行完后也会自动释放,不过当你在执行一个很大的循环时,里面会产生大量临时的局部引用,那么建议的做法是手动的调用该方法去释放这个局部引用。

  1. jweak NewWeakGlobalRef(jobject obj)
    说明:创建一个全局的弱引用
    参数:
  • obj:某个 Java 对象实例

    注意:
    弱引用不会阻止 GC 回收它引用的对象,在内存不足时,弱引用的对象往往会被回收掉,使用时一定要多加小心。

  1. void DeleteWeakGlobalRef(jweak obj)
    说明:释放某个全局的弱引用
    参数:
  • obj:某个全局弱引用

八、字符串操作

  1. jstring NewString(const jchar* unicodeChars, jsize len)
    说明:以 UTF-16 的编码方式创建一个 Java 的字符串(jchar 的定义为 uint16_t)
    参数:
  • unicodeChars:指向字符数组的指针
  • len:字符数组的长度
  1. jstring NewStringUTF(const char* bytes)
    说明:以 UTF-8 的编码方式创建一个 Java 的字符串
    参数:
  • bytes:指向字符数组的指针
  1. jsize GetStringLength(jstring string)
  2. jsize GetStringUTFLength(jstring string)
    说明:获取字符串的长度,GetStringLength 是 UTF-16 编码,GetStringUTFLength 是 UTF-8 编码
    参数:
  • string:字符串
  1. const jchar GetStringChars(jstring string, jboolean isCopy)
  2. const char GetStringUTFChars(jstring string, jboolean isCopy)
    说明:将 Java 风格的 jstring 对象转换成 C 风格的字符串,同上一个是 UTF-16 编码,一个是 UTF-8 编码
    参数:
  • string:Java 风格的字符串
  • isCopy:是否进行拷贝操作,0 为不拷贝
  1. void ReleaseStringChars(jstring string, const jchar* chars)
  2. void ReleaseStringUTFChars(jstring string, const char* utf)
    说明:释放指定的字符串指针,通常来说,Get 和 Release 是成对出现的
    参数:
  • string:Java 风格的字符串

  • chars/utf:对应的 C 风格的字符串
    示例:

    1
    2
    3
    4
    5
    6
    JNIEXPORT void JNICALL
    Java_com_afei_jnidemo_MainActivity_test(JNIEnv *env, jobject instance, jstring msg_) {
    const char *msg = env->GetStringUTFChars(msg_, 0);
    // Do Something
    env->ReleaseStringUTFChars(msg_, msg);
    }

九、数组操作

  1. jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement)
    说明:创建引用数据类型的数组
    参数:
  • length:数组的长度

  • elementClass:数组的元素所属的类

  • initialElement:使用什么样的对象来初始化,可以选择 NULL
    示例:

    1
    2
    3
    int points_count = 21;
    jclass pointFClass = env->FindClass("android/graphics/PointF");
    jobjectArray point_array = env->NewObjectArray(points_count, pointFClass, NULL);

2.ArrayType NewArray(jsize length)
说明:创建基本数据类型的数组。这里的基本数据类型有:

image-20231123154203044

参数:

  • length:数组的长度
  1. jsize GetArrayLength(jarray array)
    说明:获取数组的长度
    参数:
  • array:指定的数组对象。jarray 是 jbooleanArray、jbyteArray、jcharArray 等的父类。
  1. jobject GetObjectArrayElement(jobjectArray array, jsize index)
    说明:获取引用数据类型数组指定索引位置处的对象
    参数:
  • array:引用数据类型数组
  • index:目标索引值
  1. void SetObjectArrayElement(jobjectArray array, jsize index, jobject value)
    说明:设置引用数据类型数组指定索引位置处的值
    参数:
  • array:需要设置的引用数据类型数组
  • index:目标索引值
  • value:需要设置的值
  1. NativeType GetArrayElements(ArrayType array, jboolean isCopy)
    说明:获取基本数据类型数组的头指针
    参数:
  • array:基本数据类型数组
  • isCopy:是否进行拷贝操作,0 为不拷贝
  1. void ReleaseArrayElements(ArrayType array, NativeType* elems, jint mode)
    说明:释放基本数据类型数组指针。通常来说,Get 和 Release 是成对出现的
    参数:
  • array:基本数据类型数组
  • elems:对应的 C 风格的基本数据类型指针
  • mode:释放模式,通常我们都是使用 0,有三种,如下
    image-20231123154251199

示例:

1
2
3
4
5
Java_com_afei_jnidemo_MainActivity_test(JNIEnv *env, jobject instance, jintArray array_) {
jint *array = env->GetIntArrayElements(array_, 0);
// Do Something
env->ReleaseIntArrayElements(array_, array, 0);
}
  1. void GetArrayRegion(ArrayType array, jsize start, jsize len, NativeType* buf)
    说明:返回基本数据类型数组的部分副本。这里的基本数据类型有:

image-20231123154324154

参数:

  • array:基本数据类型数组
  • start:起始的索引值
  • len:拷贝的长度
  • buf:拷贝到的目标数组
  1. void SetArrayRegion(ArrayType array, jsize start, jsize len, const NativeType* buf)
    说明:设置基本数据类型数组元素。类型和上面的表类似。
    参数:
  • array:需要设置的基本数据类型数组
  • start:起始的索引值
  • len:需要设置的 buf 的长度
  • buf:需要设置的值数组

十、异常操作

  1. jint Throw(jthrowable obj)
    说明:抛出一个异常,需要手动创建异常的实例,调用较复杂,一般不使用这个方法
    参数:
  • obj:异常对象
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    jclass ioExceptionClazz = env->FindClass("java/io/IOException");
    jmethodID ioExceptionConstructor = env->GetMethodID(ioExceptionClazz, "<init>", "(Ljava/lang/String;)V");
    jthrowable exceptionObj = static_cast<jthrowable>(env->NewObject(ioExceptionClazz,
    ioExceptionConstructor, "IO异常"));
    if (env->Throw(exceptionObj) == JNI_OK) {
    // 创建成功
    } else {
    // 创建失败
    }
  1. jint ThrowNew(jclass clazz, const char* message)
    说明:抛出一个异常。使用起来比三个方法方便
    参数:
  • clazz:指定的异常类

  • message:异常信息
    示例:

    if (env->ThrowNew(env->FindClass(“java/io/IOException”), “IO异常”) == JNI_OK) {
    // 创建成功
    } else {
    // 创建失败
    }

  1. jthrowable ExceptionOccurred()

  2. jboolean ExceptionCheck()
    说明:检查是否有异常,如果本地函数有异常抛出,ExceptionOccurred 会返回这个异常的示例,ExceptionCheck 只返回是否有异常

  3. void ExceptionDescribe()
    说明:将异常和堆栈信息推送到错误流

  4. void ExceptionClear()
    说明:清除掉发生的异常

以上几个方法的示例:

Java 部分代码为:

public class MainActivity extends AppCompatActivity {
 
    static {
        System.loadLibrary("native-lib");
    }
 
    @Override
    protected void
    onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            test();
        } catch (Exception e) {
            Log.e("MainActivity", "onCreate: " + e);
        }
    }
 
    private native void test() throws IllegalArgumentException;
 
    private void callNullPointerException() throws NullPointerException {
        throw new NullPointerException("MainActivity NullPointerException");
    }
 
}

JNI 部分代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JNIEXPORT void JNICALL
Java_com_afei_jnidemo_MainActivity_test(JNIEnv *env, jobject instance) {
jclass clazz = env->GetObjectClass(instance);
jmethodID mid =env->GetMethodID(clazz, "callNullPointerException", "()V");
env->CallVoidMethod(instance, mid); // will throw a NullPointerException
jthrowable exc = env->ExceptionOccurred(); // 检测是否发生异常
if (exc) {
LOGD("============");
env->ExceptionDescribe(); // 打印异常信息
LOGD("============");
env->ExceptionClear(); // 清除掉发生的异常
jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(newExcCls, "throw from JNI"); // 返回一个新的异常到 Java
}
}

运行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
07-12 14:57:07.443 26623-26623/com.afei.jnidemo D/FaceAPI: ============
07-12 14:57:07.443 26623-26623/com.afei.jnidemo W/System.err: java.lang.NullPointerException: MainActivity NullPointerException
at com.afei.jnidemo.MainActivity.callNullPointerException(MainActivity.java:34)
at com.afei.jnidemo.MainActivity.test(Native Method)
at com.afei.jnidemo.MainActivity.onCreate(MainActivity.java:25)
at android.app.Activity.performCreate(Activity.java:6857)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1125)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2702)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2810)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1532)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:181)
at android.app.ActivityThread.main(ActivityThread.java:6288)
at java.lang.reflect.Method.invoke(Native Method)
07-12 14:57:07.443 26623-26623/com.afei.jnidemo W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:900)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:790)
07-12 14:57:07.443 26623-26623/com.afei.jnidemo D/FaceAPI: ============
07-12 14:57:07.444 26623-26623/com.afei.jnidemo E/MainActivity: onCreate: java.lang.IllegalArgumentException: throw from JNI
  1. void FatalError(const char* msg)
    说明:抛出一个致命异常,并且不希望JVM处理
    参数:
  • msg:致命异常的信息

十一、其它
其他还有有关 Monitor Operations、NIO Support、Reflection Support 等一些方法,由于我也没使用过,就不再这里解释了。
可以参考 JNI 的官网的介绍:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp9502

原文链接:JNI 方法大全及使用示例-CSDN博客