JNI(Java Native Interface)是Java语言提供的一种机制,用于在Java应用程序中调用本地代码(Native Code)或者让本地代码调用Java代码。它为Java和其他编程语言(如C、C++)之间的交互提供了一种标准的接口。
使用JNI,可以通过以下步骤实现Java和本地代码之间的交互:
- 定义Native方法:在Java类中声明一个native修饰符的方法,表示该方法是一个本地方法,实现将在本地代码中定义。
- 生成包含本地方法的头文件:使用Javac命令编译包含Native方法的Java源文件,然后使用javah命令生成头文件(.h文件),该头文件将包含本地方法的声明。
- 实现本地代码:在C或C++源文件中编写与Java中声明的Native方法对应的函数,可以使用JNI提供的API来访问Java对象、调用Java方法等。
- 编译本地代码:编译包含本地代码的源文件,并生成共享库(动态链接库或静态库)。
- 在Java中加载本地库:使用System.loadLibrary()方法或System.load()方法在Java中加载本地库。这将使得本地代码可在Java环境中被访问和调用。
- 调用本地方法:在Java中通过调用Native方法来调用本地代码。本地代码将执行与该方法对应的本地实现,并返回结果给Java。
JNI提供了一系列的API来支持Java和本地代码之间的交互,包括访问Java对象、调用Java方法、异常处理等。它还提供了一些功能,如线程绑定、字符串转换、数组访问等。
使用JNI可以充分利用Java的跨平台性和易用性,并在需要使用本地代码的场景下提供灵活性和性能优势。
数据类型
在JNI中,有一系列的数据类型用于表示Java和本地代码之间的数据交互。这些数据类型对应了Java的数据类型,并提供了在Java和本地代码之间进行数据转换的功能。
以下是一些常用的JNI数据类型:
基本数据类型:
jboolean | 对应Java的boolean类型。 |
jbyte | 对应Java的byte类型。 |
jchar | 对应Java的char类型。 |
jshort | 对应Java的short类型。 |
jint | 对应Java的int类型。 |
jlong | 对应Java的long类型。 |
jfloat | 对应Java的float类型。 |
jdouble | 对应Java的double类型。 |
引用类型:
jobject | 对应Java的任意对象类型。 |
jclass | 对应Java的Class类型。 |
jstring | 对应Java的String类型。 |
jarray | 对应Java的数组类型。 |
jthrowable | 对应Java的Throwable类型。 |
其他类型:
jbooleanArray | 对应Java的boolean[]类型。 |
jbyteArray | 对应Java的byte[]类型。 |
jcharArray | 对应Java的char[]类型。 |
jshortArray | 对应Java的short[]类型。 |
jintArray | 对应Java的int[]类型。 |
jlongArray | 对应Java的long[]类型。 |
jfloatArray | 对应Java的float[]类型。 |
jdoubleArray | 对应Java的double[]类型。 |
在JNI中,还有一些用于表示方法签名、字段描述符等的数据类型,如jmethodID、jfieldID等。
这些JNI数据类型可以在Java和本地代码之间进行数据转换,使得我们可以在Java代码和本地代码之间传递参数、返回结果,以及访问对象的属性和方法。
基本方法
在JNI中,有几种方法用于在Java和本地代码之间进行调用和交互。以下是一些常用的JNI方法:
JNIEnv结构体中的方法:
GetMethodID: | 通过方法名和方法签名获取Java类中的方法ID。 |
Call<Type>Method: | 调用Java对象的实例方法,并返回相应类型的结果。 |
CallStatic<Type>Method: | 调用Java类的静态方法,并返回相应类型的结果。 |
NewObject: | 创建Java类的新对象,并调用其构造函数初始化。 |
Get<Type>Field: | 获取Java对象字段的值。 |
Set<Type>Field: | 设置Java对象字段的值。 |
JNIEnv结构体中的数组方法:
New<Type>Array: | 创建Java数组对象。 |
Get<Type>ArrayElements: | 获取Java数组的元素。 |
Release<Type>ArrayElements: | 释放Java数组的元素。 |
GetArrayLength: | 获取Java数组的长度。 |
字符串相关方法:
NewStringUTF: | 从C字符串创建Java字符串对象。 |
GetStringUTFChars: | 将Java字符串转换为C字符串。 |
ReleaseStringUTFChars: | 释放通过GetStringUTFChars获取的C字符串。 |
异常处理方法:
ExceptionCheck: | 检查是否有异常发生。 |
ExceptionOccurred: | 获取当前抛出的异常对象。 |
ExceptionDescribe: | 打印异常堆栈信息。 |
ExceptionClear: | 清除当前的异常状态。 |
上述方法只是JNI提供的一部分方法,用于实现Java和本地代码之间的交互。通过这些方法,可以在Java中调用本地代码的函数,也可以在本地代码中调用Java对象的方法。
需要注意的是,在使用JNI调用Java方法时,需要根据方法的签名(包括方法名、入参类型和返回值类型)正确地进行匹配。另外,还需要注意内存管理和异常处理,以确保程序的正确性和稳定性。
签名
在JNI中,方法签名用来表示Java方法的参数类型和返回值类型。JNI使用一种特定的格式来表示方法签名,以便在Java和本地代码之间进行正确的类型匹配。
方法签名的基本格式如下:
(参数类型1, 参数类型2, ...)返回值类型
其中,参数类型和返回值类型使用特定的字母缩写表示。以下是常见的类型缩写:
Z | boolean类型 |
B | byte类型 |
C | char类型 |
S | short类型 |
I | int类型 |
J | long类型 |
F | float类型 |
D | double类型 |
V | void类型 |
L<类名>; | 引用类型,例如Ljava/lang/String;表示String类型 |
对于数组类型,可以使用[来表示数组,例如[I表示int[]类型。
以下是几个示例方法签名的示例:
- public int add(int a, int b) 的签名为 (II)I
- public void printMessage(String message) 的签名为 (Ljava/lang/String;)V
- public static native double calculateAverage(int[] numbers) 的签名为 ([I)D
可以使用工具或者手动将Java方法的参数类型和返回值类型转换为对应的JNI签名。
在JNI中,使用方法签名来获取方法ID、调用方法等操作,以确保Java和本地代码之间的正确交互。
JNIEnv
JNIEnv(Java Native Interface Environment)是Java Native Interface(JNI)提供的一个关键结构体,用于在Java代码和本地代码之间进行交互和通信。JNIEnv提供了一组函数指针,用于调用Java对象的方法、访问字段、操作数组等。
JNIEnv结构体定义如下:
typedef const struct JNINativeInterface *JNIEnv; |
JNIEnv结构体中包含了一系列函数指针,这些函数指针定义在JNINativeInterface结构体中,可以通过JNIEnv结构体进行调用。具体的函数指针包括以下一些常见的函数:
GetMethodID | 通过方法名和方法签名获取Java类中方法的ID。 |
Call<Type>Method | 调用Java对象的实例方法,并返回相应类型的结果。 |
CallStatic<Type>Method | 调用Java类的静态方法,并返回相应类型的结果。 |
NewObject | 创建Java类的新对象,并调用其构造函数初始化。 |
Get<Type>Field | 获取Java对象字段的值。 |
Set<Type>Field | 设置Java对象字段的值。 |
New<Type>Array | 创建Java数组对象。 |
Get<Type>ArrayElements | 获取Java数组的元素。 |
Release<Type>ArrayElements | 释放Java数组的元素。 |
除了上述函数指针,JNIEnv结构体还包含了其他一些函数指针,用于异常处理、类和对象操作等。
在JNI中,JNIEnv结构体是在本地代码中使用的重要接口,它提供了许多函数指针,用于与Java代码进行交互和通信。通过JNIEnv,本地代码可以调用Java对象的方法、访问字段、创建对象等操作。
举个例子
JNI实现
#include <jni.h>
#include <string>
#include <utils/Log.h>
#define LOG_TAG "CSKMicArrayControl-JNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C" {
#include "swcskaudio.h"
JNIEXPORT jint JNICALL
Java_com_example_media_CSKMicArrayControl_native_1AudioRecord_1setup(
JNIEnv *env,
jobject thiz,
jint audioFormat,
jint channels,
jint rate) {
// TODO: implement native_AudioRecord_setup()
jint result;
result = sw_csk_audio_open(channels, rate, audioFormat);
if (result < 0) {
LOGE("sw_csk_audio_open filed!");
return -1;
}
return result;
}
struct audiorecord_callback_cookie {
JNIEnv *env;
jclass clazz = nullptr;
jmethodID methodID;
bool busy;
};
static audiorecord_callback_cookie callbackInfo;
// typedef void (*send_record_data)(char *, unsigned int);
static void sendRecordDataCallback(char *recordData, int length) {
// Creating Arrays in Java Objects
jbyteArray array = callbackInfo.env->NewByteArray(length);
jbyte* buf = callbackInfo.env->GetByteArrayElements(array, NULL);
// copy ByteArray data to Java Objects
callbackInfo.env->SetByteArrayRegion(array, 0, length,
reinterpret_cast<jbyte *>(recordData));
callbackInfo.env->CallStaticVoidMethod(callbackInfo.clazz,
callbackInfo.methodID, array, length);
callbackInfo.env->ReleaseByteArrayElements(array, buf, JNI_ABORT);
callbackInfo.env->DeleteLocalRef(array);
}
JNIEXPORT jint JNICALL
Java_com_example_media_CSKMicArrayControl_native_1AudioRecord_1start(
JNIEnv *env,
jobject thiz,
jint mode) {
// TODO: implement native_AudioRecord_start()
if (1 == mode) {
callbackInfo.env = env;
callbackInfo.clazz = env->GetObjectClass(thiz);
callbackInfo.methodID = env->GetStaticMethodID(callbackInfo.clazz,
"receiveRecordData",
"([BI)V");
callbackInfo.busy = false;
sw_start_audio_record_th(sendRecordDataCallback);
}
return 0;
}
JNIEXPORT jint JNICALL
Java_com_example_media_CSKMicArrayControl_native_1AudioRecord_1stop(
JNIEnv *env,
jobject thiz) {
// TODO: implement native_AudioRecord_stop()
return 0;
}
JNIEXPORT jint JNICALL
Java_com_example_media_CSKMicArrayControl_native_1AudioRecord_1release(
JNIEnv *env,
jobject thiz) {
// TODO: implement native_AudioRecord_release()
sw_csk_audio_close();
return 0;
}
}
Java 调用
package com.example.media;
import android.util.Log;
public class CSKMicArrayControl {
private static final String TAG = "CSKMicArrayControl";
static {
System.loadLibrary("CSKMicArrayControl");
}
public native static String native_getMicroModel();
public native static String native_getMicroVersion();
public native int native_getMicroStatus();
public native int native_getPattern();
public native int native_AudioRecord_setup(int audioFormat, int channels, int rate);
public native int native_AudioRecord_start(int mode);
public native int native_AudioRecord_stop();
public native int native_AudioRecord_release();
public native int native_AudioRecord_readInArray(byte[] audioData, int sizeInBytes, int readMode);
public static void onWakeUp(String mWakeUpMsg) {
Log.i(TAG, "DCXLOG ==> onWakeUp");
}
public static void onEventMessage(String mEventMsg) {
Log.i(TAG, "DCXLOG ==> onEventMessage");
}
public static void receiveRecordData(byte[] audioData, int sizeInBytes) {
Log.i(TAG, "DCXLOG ==> receiveRecordData" + audioData.toString() + "len:" + sizeInBytes);
}
}
以上例子种实现了一个将音频数据往应用层上抛的逻辑,通过回调方式;