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);
}
}
以上例子种实现了一个将音频数据往应用层上抛的逻辑,通过回调方式;
文章详细介绍了JavaNativeInterface(JNI)如何在Java和C/C++之间建立桥梁,包括定义Native方法、生成头文件、实现本地代码、数据类型转换、JNI方法、签名、JNIEnv结构以及一个实际的JNI实现示例。
972

被折叠的 条评论
为什么被折叠?



