前言
概念
本文中:
JNI方法:指JNI提供的一系列API。
native方法:跨native层调用的方法(Java->C/C++)。
C/C++方法:除native方法外,普通的C/C++方法。
native层:C/C++代码。
代码
示例代码:JNIInterface
测试代码:JNIInterfaceTest
摘要
本文主要内容如下:
加载so
在Java层调用native方法,首先要加载so。加载so,主要通过下面两个方法,二者效果是一致的:
void load(String filename)
void loadLibrary(String libname)
以加载libjava2cpp.so
为例:
load的参数是so文件的全路径名。APK安装后,在设备的/data/data/{packageName}/lib
下可以看见APK里的so。
所以:
System.load("/data/data/com.example.java2cpp/lib/libjava2cpp.so");
loadLibrary的参数,是so文件的部分名称。如libjava2cpp.so
,就是去掉前缀"lib",去掉后缀".so":
System.loadLibrary("java2cpp");
注意事项:
- 加载so的代码,通常写在静态代码块里。好处是加载类时,静态代码块会优先执行。确保了在用户调用类的native方法之前,so已加载。
class JNIInterface {
static {
System.loadLibrary("java2cpp");
}
}
native方法声明、定义
在C/C++中,声明和定义通常是分离的。声明一般在头文件(.h)中,定义一般在源文件(.cpp/.cc)中。
native方法的声明和定义也是如此。但是特别的是,定义是在Java源文件中的。
class JNIInterface {
public native static String stringFromJNI();
}
native的定义则是在C/C++的源文件中。
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_java2cpp_JNIInterface_stringFromJNI(JNIEnv *env, jclass clazz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
注意事项:
- 声明是在Java源文件中,需要遵守Java语法。没有方法体,并且需要用
native
修饰符来声明。 - 定义是在C/C++的源文件中。需要遵守C/C++的语法。
- native方法声明和定义的参数个数不一样。定义总会比声明多两个参数:
JNIEnv
和jclass
/jobject
。 - native方法是不支持重载的。如,再声明一个stringFromJNI的重载方法是不可行的。
public native static String stringFromJNI(int x);
- 推测:
extern "C"
导致了native方法无法重载。extern "C"
会让编译器按照C语言的编译方式,为native方法生成符号表。而C语言是不支持重载的。
native与static native
native方法,可以声明为成员方法,也可以声明为静态方法
声明:
class JNIInterface {
public native void jniMethod();
public native static void staticJniMethod();
}
对应的实现:
extern "C" JNIEXPORT void JNICALL
Java_com_example_java2cpp_JNIInterface_jniMethod(JNIEnv *env, jobject thiz) {}
extern "C" JNIEXPORT void JNICALL
Java_com_example_java2cpp_JNIInterface_staticJniMethod(JNIEnv *env, jclass clazz) {}
两者的差异在于:
- 成员方法对应的实现,第二个参数是jobject,代表JNIInterface的一个对象
- 静态方法对应的实现,第二个参数是jclass,代表JNIInterface这个类
静态注册与动态注册
native方法,可以选择静态注册、动态注册两种方式。
下面将对如下两个native方法分别采用静态注册、动态注册:
public native void nativeJniMethod();
public native int nativeDynamicRegisterMethod();
静态注册
静态注册的native方法名,必须遵循一定的规则:
extern "C" JNIEXPORT
+ 返回值 +JNICALL
Java_
+ 包名(_
代替.
) +_
+ 类名 +_
+ 方法名
extern "C" JNIEXPORT void JNICALL
Java_com_example_java2cpp_JNIInterface_nativeJniMethod(JNIEnv *env, jobject thiz) {}
动态注册
动态注册一般在JNI_OnLoad
方法上进行。JNI_OnLoad方法会在System.loadLibrary
加载so成功后,被虚拟机调用。
动态注册主要分为四步:
-
实现native方法:dynamic_register_method。命名和普通的c++方法一样即可。
-
在gMethods数组中,添加
JNINativeMethod
结构体(代表native方法声明与native方法实现映射关系),如:
{"nativeDynamicRegisterMethod", "()I", (void *) dynamic_register_method}
- 通过
FindClass
来加载对应的Java类。 - 通过
RegisterNatives
来进行动态注册。
后续需要新增native方法时,只需要重复前两个步骤即可。
#define JNIInterface_CLASS "com/example/java2cpp/JNIInterface"
static jint dynamic_register_method(JNIEnv *env, jobject thiz) {
return 0;
}
/**
* 代表native方法声明与native方法实现映射关系
* 结构体JNINativeMethod的三个字段分别是:
* name:Java定义的native方法名
* signature:Java定义的native方法的类型签名
* fnPtr:native方法的函数指针,指向native方法的实现
*/
static const JNINativeMethod gMethods[] = {
{"nativeDynamicRegisterMethod", "()I", (void *) dynamic_register_method},
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
jint result = JNI_ERR;
if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
return result;
}
jclass c = env->FindClass(JNIInterface_CLASS);
if (c == nullptr) {
const char *msg = "Native registration unable to find class; aborting...";
env->FatalError(msg);
}
int numMethods = sizeof(gMethods) / sizeof(gMethods[0]);
//采用RegisterNatives进行动态注册
if (env->RegisterNatives(c, gMethods, numMethods) < 0) {
const char *msg = "RegisterNatives failed; aborting...";
env->FatalError(msg);
}
return JNI_VERSION_1_6;
}
还可以对动态注册的方法进行反注册,一般在JNI_OnUnload
进行,通过UnregisterNatives
来反注册。
void JNI_OnUnload(JavaVM *vm, void *reserved) {
...
env->UnregisterNatives(c)
}
Java元素定位
在native方法中,无论是获取Java对象的变量、还是调用Java的方法等,首先需要通过JNI方法,定位对应的Java元素。
这里的Java元素,包括:类、成员变量、静态变量、成员方法、静态方法等。
Java代码:
class JNIInterface {
public int num = 1;
public static int staticNum = 2;
private int getNum() {
return num;
}
private static int getStaticNum() {
return staticNum;
}
}
C++代码:
struct JavaJNIInterface {
jclass class_ref;
jfieldID num;
jfieldID static_num;
jmethodID getNum;
jmethodID getStaticNum;
};
static JavaJNIInterface javaJNIInterface;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
...
jclass c = env->FindClass(JNIInterface_CLASS);
javaJNIInterface.class_ref = reinterpret_cast<jclass>(env->NewGlobalRef(c));
javaJNIInterface.num = env->GetFieldID(c, "num", "I");
javaJNIInterface.static_num = env->GetStaticFieldID(c, "staticNum", "I");
javaJNIInterface.getNum = env->GetMethodID(c, "getNum", "()I");
javaJNIInterface.getStaticNum = env->GetStaticMethodID(c, "getStaticNum", "()I");
env->DeleteLocalRef(c);
...
}
注意事项:
jfieldID
、jmethodID
是可以反复使用的,除非虚拟机卸载了该类。所以,这里将jfieldID
、jmethodID
结果保存起来。jclass
在保存之前,经过了NewGlobalRef
转换。因为FindClass
获得的jclass
是局部引用,生命周期短。具体原因参考后面的局部引用与全局引用。- 变量/方法的是public,还是private都不影响JNI方法访问。
- 字段、方法的类型签名不需要刻意去记忆。可以通过Studio的提示,快速实现。
局部引用与全局引用
JNI的引用类型具体有哪些,可以回顾一下《从Java到C+±JNI基本概念》。
JNI将引用类型分为两类:局部引用、全局引用。
当Java方法调用native方法时,Java VM会创建一个注册表。所有从Java层传递到native层的Java对象会被添加到注册表中。注册表会将不可移动的局部引用映射到Java对象,防止对象被垃圾回收。native方法调用结束并返回时,注册表会被删除,局部引用不再指向Java对象。这些Java对象如果没有其他GC Roots可达,就可正常被垃圾回收。
局部引用
- 在一个native方法调用期间都是有效的,在native方法完成调用返回时,会被自动释放。不能跨线程使用。
- Java对象作为参数,传递到native方法时,都是局部引用。
- 通过JNI方法,获取到的Java对象,都是局部引用。如:
FindClass
、NewObject
、GetObjectField
等JNI方法。 - 局部引用在下面两种情况要考虑通过
env->DeleteLocalRef
主动释放:- native方法访问大型的Java对象时(比如一个大数组),会创建对Java对象的本地引用。native方法使用完大对象后,还进行较耗时的操作。这期间,由于本地引用的存在,会导致大对象无法及时被垃圾回收。
- native方法创建了大量的局部引用,可能导致本地引用表溢出,甚至系统内存不足。比如在循环遍历中,调用JNI方法,获取Java对象,不断创建新的本地引用。下列代码,将会导致
JNI ERROR (app bug): local reference table overflow (max=8388608)
。
static void create_local_ref_too_much(JNIEnv *env, jclass clazz) {
for (int i = 0; i < 10000000; i++) {
jclass c = env->FindClass(JNIInterface_CLASS);
}
}
运行单测testCreateLocalRefTooMuch
,在logcat里将会看到如下结果:
全局引用
- 明确释放之前,都是有效的。可以跨方法、跨线程使用。
- 全局引用是用局部引用来创建:
jclass local_ref_class = env->FindClass(JNIInterface_CLASS);
jclass global_ref_class = reinterpret_cast<jclass>(env->NewGlobalRef(local_ref_class));
- 全局引用创建后,不像局部引用,可以被自动释放,只能手动释放:
env->DeleteGlobalRef(global_ref_class);
- 不及时释放不需要的全局引用,可能会导致全局引用表溢出。下列代码,将会导致
JNI ERROR (app bug): global reference table overflow (max=51200)
。
static void create_global_ref_too_much(JNIEnv *env, jclass clazz) {
for (int i = 0; i < 10000000; i++) {
jclass c = env->FindClass(JNIInterface_CLASS);
jclass global_ref = reinterpret_cast<jclass>(env->NewGlobalRef(c));
env->DeleteLocalRef(c);
}
}
运行单测testCreateGlobalRefTooMuch
,在logcat里将会看到如下结果:
传递Java基本类型
Java的基本类型,都能在JNI里找到对应的类型。对应表详见《从Java到C++:JNI基本概念》。如:
Java Type | Native Type | Description |
---|---|---|
int | jint | signed 32 bits |
int对应jint。而jint在头文件jni.h
的定义为:
typedef int32_t jint; /* signed 32 bits */
int32_t其实只是C++中int的别名。
typedef __int32_t int32_t;
typedef int __int32_t;
Java的基本类型与C++的基本类型的对应关系如下:
注意事项:
该表格仅在Android平台上有效。
因为C++与Java不同,它是平台相关的。Java的基本数据类型是固定长度的。但C++不提供这种保证。C++提供了一种灵活的标准,它只确保最小长度(从C语言借鉴而来),如下所示:-
short至少16位;
-
int至少与short一样长;
-
long至少32位,且至少与int一样长;
-
long long至少64位,且至少与long一样长
-
- Java的
long
对应C++的long long
,不是long
- Java的char是16位的,采用的是Unicode编码,两个字节表示一个字符。对应C++的
unsigned short
。而C++的char
是8位的。 - Java的byte对应C++的
signed char
。C++中的char
与C++中的int
、short
(默认是有符号类型)不同,char
默认是有符号和无符号,由C++实现决定。经测试,在Android平台,char是等同于signed char
的。
测试代码如下:
Java代码:
public native static void nativeTransmitPrimitiveType(int i, long l, float f,
byte _byte, double d, boolean b, short s, char c);
@Test
public void testTransmitPrimitiveType() {
JNIInterface.nativeTransmitPrimitiveType(Integer.MAX_VALUE, Long.MAX_VALUE, Float.MAX_VALUE,
Byte.MAX_VALUE, Double.MAX_VALUE, true, Short.MAX_VALUE, '中');
}
C++代码:
static void transmit_primitive_type(JNIEnv *env, jclass clazz, jint i, jlong l, jfloat f,
jbyte byte, jdouble d, jboolean b, jshort s, jchar c) {
int c_i = numeric_limits<int>::max();
assert(i == c_i);
//long等同于long int
assert(i == numeric_limits<long>::max());
long long c_l = numeric_limits<long long>::max();
assert(l == c_l);
assert(l != numeric_limits<unsigned long long>::max());
assert(l == numeric_limits<long long int>::max());
float c_f = numeric_limits<float>::max();
assert(f == c_f);
signed char c_byte = numeric_limits<signed char>::max();
assert(byte == c_byte);
assert(byte == numeric_limits<char>::max());
assert(255 == numeric_limits<unsigned char>::max());
double c_d = numeric_limits<double>::max();
assert(d == c_d);
bool c_b = numeric_limits<bool>::max();
assert(b == c_b);
short c_s = numeric_limits<short>::max();
assert(s == c_s);
unsigned short c_c = 0x4e2d;//'中'的Unicode编码
assert(2 == sizeof(unsigned short));
assert(c == c_c);
}
传递Java对象
Java的引用类型与JNI引用类型的对应表详见《从Java到C+±JNI基本概念》。
在native方法中,可以实现:
- 获取Java对象的各种变量的值
- 修改Java对象的各种变量
- 创建Java对象
Java对象定义如下:
public class Position {
public float longitude;
public float latitude;
}
public class Image {
public long id;
public int width;
public int height;
public Position pos = new Position();
public byte[] data = new byte[10];
}
C++代码:
- 定位Java类Image、Position的相关元素:类、变量
struct JavaPosition {
jclass class_ref;
jfieldID longitude;
jfieldID latitude;
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
...
jclass c = env->FindClass("com/example/java2cpp/bean/Image");
javaImage.class_ref = reinterpret_cast<jclass>(env->NewGlobalRef(c));
javaImage.id = env->GetFieldID(c, "id", "J");
javaImage.width = env->GetFieldID(c, "width", "I");
javaImage.height = env->GetFieldID(c, "height", "I");
javaImage.position = env->GetFieldID(c, "pos", "Lcom/example/java2cpp/bean/Position;");
javaImage.data = env->GetFieldID(c, "data", "[B");
env->DeleteLocalRef(c);
c = env->FindClass("com/example/java2cpp/bean/Position");
javaPosition.class_ref = reinterpret_cast<jclass>(env->NewGlobalRef(c));
javaPosition.latitude = env->GetFieldID(c, "latitude", "F");
javaPosition.longitude = env->GetFieldID(c, "longitude", "F");
env->DeleteLocalRef(c);
...
}
- 在native方法中,通过JNI的一系列方法,获取Java对象的变量的值
//获取对象的基本类型变量:通过Get<PrimitiveType>Field系列方法
jlong id = env->GetLongField(image, javaImage.id);
jint width = env->GetIntField(image, javaImage.width);
jint height = env->GetIntField(image, javaImage.height);
assert(id == 0);
assert(width == 0);
assert(height == 0);
//获取对象的普通引用类型变量:通过GetObjectField
jobject position = env->GetObjectField(image, javaImage.position);
if (position != nullptr) {
jfloat longitude = env->GetFloatField(position, javaPosition.longitude);
jfloat latitude = env->GetFloatField(position, javaPosition.latitude);
assert(longitude == 0.0f);
assert(latitude == 0.0f);
}
//获取对象的数组变量:也是通过GetObjectField,需要强转为对应的JNI引用类型,如:
//byte[]<->jbyteArray
//int[]<->jintArray
jbyteArray data = reinterpret_cast<jbyteArray>( env->GetObjectField(image, javaImage.data));
//获取Java数组内容
if (data != nullptr) {
jsize size = env->GetArrayLength(data);
jboolean isCopy;
//基本类型数组:使用Get<PrimitiveType>ArrayElements系列方法。
//引用类型数组:使用GetObjectArrayElement
jbyte *byte_array_native = env->GetByteArrayElements(data, &isCopy);
for (int i = 0; i < size; ++i) {
jbyte b = byte_array_native[i];
assert(b == 0);
}
//第三个参数mode通常为0即可
env->ReleaseByteArrayElements(data, byte_array_native, 0);
}
- 修改Java对象的变量的值
//修改对象的基本类型变量:通过Set<PrimitiveType>Field系列方法
env->SetLongField(image, javaImage.id, 999);
env->SetIntField(image, javaImage.width, 1920);
env->SetIntField(image, javaImage.height, 1080);
//修改对象的普通引用类型变量:通过SetObjectField
//创建一个Java对象:通过AllocObject
jobject newPosition = env->AllocObject(javaPosition.class_ref);
env->SetFloatField(newPosition, javaPosition.longitude, 9.9f);
env->SetFloatField(newPosition, javaPosition.latitude, 99.9f);
env->SetObjectField(image, javaImage.position, newPosition);
//修改对象的数组类型变量:也是通过SetObjectField
//创建Java基本类型的数组:通过New<PrimitiveType>Array系列方法
//创建Java引用类型的数组:通过NewObjectArray
jbyteArray newData = env->NewByteArray(10);
int len = 1;
for (int i = 0; i < 10; ++i) {
jbyte b = i;
//基本类型数组通过Set<PrimitiveType>ArrayRegion系列方法设置数组的元素
//Set<PrimitiveType>ArrayRegion其实可以直接设置整个C++数组给newData;
//如果是引用类型数组,则要通过SetObjectArrayElement一个个设置数组的元素
env->SetByteArrayRegion(newData, i, len, &b);
}
env->SetObjectField(image, javaImage.data, newData);
//直接修改Java数组的内容:大体流程与获取Java数组内容一致
jbyteArray extraData = reinterpret_cast<jbyteArray>(env->GetObjectField(image,
javaImage.extra_data));
if (data != nullptr) {
jsize size = env->GetArrayLength(extraData);
jboolean isCopy;
jbyte *byte_array_native = env->GetByteArrayElements(extraData, &isCopy);
for (int i = 0; i < size; i++) {
//更改数组的内容
byte_array_native[i] = i;
}
//将缓冲数组的内容拷贝回原数组,释放缓冲数组。
env->ReleaseByteArrayElements(extraData, byte_array_native, 0);
}
注意事项:
-
获取Java对象的各种变量的值
- 获取对象的基本类型变量:通过
Get<PrimitiveType>Field
系列方法 - 获取对象的普通引用类型变量:通过
GetObjectField
方法 - 获取对象的数组类型变量:也是通过
GetObjectField
方法,还需要强转为对应的JNI引用类型。比如:byte[]
<->jbyteArray
、int[]
<->jintArray
- 获取静态变量:和获取上述的成员变量类似,只是方法名的Get后面多了一个
Static
。方法的第一个参数也由jobject
,变成了jclass
。- 基本类型:通过
GetStatic<PrimitiveType>Field
系列方法 - 普通引用类型、数组类型:通过
GetStaticObjectField
方法
- 基本类型:通过
- 获取Java数组内容:
- 获取数组大小:通过
GetArrayLength
方法 - 获取数组内容:
- 基本类型数组:通过
Get<PrimitiveType>ArrayElements
系列方法 - 引用类型数组:通过
GetObjectArrayElement
方法
- 基本类型数组:通过
- 获取Java数组后,需要释放缓冲数组:通过
ReleaseByteArrayElements
方法
- 获取数组大小:通过
- 获取对象的基本类型变量:通过
-
修改Java对象的各种变量
- 修改对象的基本类型变量:通过
Set<PrimitiveType>Field
系列方法 - 修改对象的普通引用类型变量:通过
SetObjectField
方法 - 修改对象的数组类型变量:也是通过
SetObjectField
方法 - 修改Java数组的元素:
- 基本类型数组:通过
Set<PrimitiveType>ArrayRegion
系列方法设置数组的元素。Set<PrimitiveType>ArrayRegion
其实可以直接设置整个C++数组给Java数组 - 引用类型数组:通过
SetObjectArrayElement
方法一个个设置数组的元素
- 基本类型数组:通过
- 修改对象的基本类型变量:通过
-
创建Java对象
- 创建普通Java对象:通过
AllocObject
方法 - 创建数组:
- 基本类型的数组:通过
New<PrimitiveType>Array
系列方法 - 引用类型的数组:通过
NewObjectArray
- 基本类型的数组:通过
- 创建普通Java对象:通过
测试代码:testFillImage
调用Java方法
在native方法中,可以通过JNI方法调用各种Java方法。无论Java方法是public,还是private,都不会影响JNI的调用。
Java代码:
public class Image {
private static final String TAG = "Image";
...
public Position getPos() {
return pos;
}
private byte[] getData() {
return data;
}
}
class JNIInterface {
public native static Position nativeGetImagePos(Image image);
public native static byte[] nativeGetImageData(Image image);
public native static int nativeAdd(int i, int j);
}
C++代码:
- 元素定位:略
- 在native中调用对应的Java方法
static jobject get_image_pos(JNIEnv *env, jclass clazz, jobject image) {
jobject pos = env->CallObjectMethod(image, javaImage.getPos);
return pos;
}
static jbyteArray get_image_data(JNIEnv *env, jclass clazz, jobject image) {
jbyteArray data = reinterpret_cast<jbyteArray>(env->CallObjectMethod(image, javaImage.getData));
return data;
}
static jint add(JNIEnv *env, jclass clazz, jint i, jint j) {
jint result = env->CallStaticIntMethod(javaCalculator.class_ref, javaCalculator.add, i, j);
return result;
}
注意事项:
- Java成员方法通过
Call<return type>Method
系列的方法调用,如CallObjectMethod
、CallIntMethod
、CallVoidMethod
等。- 参数1固定是Java对象,参数2固定是方法Id。
- 后续参数是Java方法参数,没有则不传。
- Java静态方法通过
CallStatic<return type>Method
系列的方法调用,如CallStaticObjectMethod
、CallStaticIntMethod
、CallStaticVoidMethod
等。- 参数1固定是Java类,参数2固定是方法Id。
- 后续参数是Java方法参数,没有则不传。
测试代码:testCallJavaMethodInNative
处理Java调用异常
C++调用Java方法时,如果发生了异常,需要通过JNI的相关方法进行处理。
Java代码:
class JNIInterface {
private static void javaThrowException() {
throw new NullPointerException();
}
public native static int nativeHandleJavaException();
}
C++代码:
-
元素定位:略
-
处理调用Java方法时发生的异常
jint handle_java_exception(JNIEnv *env, jclass clazz) {
//产生了一个Java异常
env->CallStaticVoidMethod(javaJNIInterface.class_ref, javaJNIInterface.javaThrowExceptMethod);
//在Clear之前,不能调用除ExceptionXxx系列之外的JNI方法
//env->FindClass("com/example/java2cpp/JNIInterface");
if (env->ExceptionCheck()) {
env->ExceptionClear();
return -1;
} else {
return 0;
}
}
注意事项:
- 先通过
ExceptionCheck
检查方法调用是否产生异常,然后通过ExceptionClear
处理。ExceptionClear
的作用相当于try catch
。 - 在Clear之前,不能调用除
ExceptionXxx
系列之外的JNI方法。 - 还可以通过
ExceptionDescribe
获取异常描述,通过ExceptionOccurred
获取异常对象。暂不提供代码示例。
测试代码:testHandleJavaException
、testUnHandleJavaException
传递枚举
Java枚举实际上是个对象。而C++的枚举,可以自动转换为int值,或者由int强转为枚举。
传递Java枚举到C++层,或者转换C++枚举到Java层,需要做一些额外的处理。
Java代码:
public enum ImageFormat {
RGB_888, NV21, NV12
}
class JNIInterface {
public native static ImageFormat nativeTransmitEnum(int iFormat, ImageFormat format);
}
C++代码:
- C++层对应枚举:
enum image_format {
RGB_888, NV21, NV12
};
- 元素定位:
jclass c = env->FindClass("com/example/java2cpp/bean/ImageFormat");
javaImageFormat.class_ref = reinterpret_cast<jclass>(env->NewGlobalRef(c));
javaImageFormat.name = env->GetMethodID(c, "name", "()Ljava/lang/String;");
javaImageFormat.ordinal = env->GetMethodID(c, "ordinal", "()I");
javaImageFormat.values = env->GetStaticMethodID(c, "values",
"()[Lcom/example/java2cpp/bean/ImageFormat;");
env->DeleteLocalRef(c);
- 转换Java枚举成C++枚举、转换C++枚举成Java枚举:
static jobject transmit_enum(JNIEnv *env, jclass clazz, jint i_format, jobject format) {
//如果传的是java枚举的下标
//c++的枚举,可以自动转为int
assert(i_format == image_format::NV21);
//int无法自动转换为c++枚举,需要强转
image_format nv21 = static_cast<image_format>(i_format);
assert(nv21 == image_format::NV21);
//调用Java枚举的ordinal方法,获取对应枚举的下标
image_format c_format = static_cast<image_format> (env->CallIntMethod(format,
javaImageFormat.ordinal));
assert(c_format == image_format::NV21);
image_format c_rgb = image_format::RGB_888;
//调用Java枚举的静态方法values,获取枚举集合
jobjectArray values = reinterpret_cast<jobjectArray>(env->CallStaticObjectMethod(
javaImageFormat.class_ref, javaImageFormat.values));
//获取对应的Java枚举
jobject java_format = env->GetObjectArrayElement(values, c_rgb);
return java_format;
}
注意事项:
- 转换过程中,需要用到Java枚举的
ordinal
、values
方法。 - C++的枚举,可以自动转换为int值。但反之则不行,需要强转。
- 如代码中所示,直接传递Java枚举进入native,还得通过Java枚举的
ordinal
方法来获取枚举的下标。所以建议直接传递枚举的下标给native层。
测试代码:testTransmitEnum
传递字符串
在Java、C++之间传递字符串,需要注意字符串编码的格式。
传递UTF-8编码
static jstring transmit_string(JNIEnv *env, jclass clazz, jstring s) {
jboolean isCopy;
const char *chars = env->GetStringUTFChars(s, &isCopy);
std::string c_s = "中国";
assert(strcmp(chars, c_s.c_str()) == 0);
char c_chars[7];
c_chars[0] = 0xE4;//E4B8AD即"中"的UTF-8编码
c_chars[1] = 0xB8;
c_chars[2] = 0xAD;
c_chars[3] = 0xE5;//E59BBD即"国"的UTF-8编码
c_chars[4] = 0x9B;
c_chars[5] = 0xBD;
c_chars[6] = '\0';//'\0',C语言里用来表示字符串的结尾
assert(strcmp(chars, c_chars) == 0);
//使用完,要及时释放
env->ReleaseStringUTFChars(s, chars);
return env->NewStringUTF(c_chars);
}
注意事项:
- native方法获取到Java字符串,使用
GetStringUTFChars
。 - 使用完后,必须调用
ReleaseStringUTFChars
释放获取到的字符数组指针。因为GetStringUTFChars
调用了C++的关键字new,创建对应的字符数组。 - native方法返回字符串给Java层,需要先调用
NewStringUTF
创建Java字符串。
测试代码:testTransmitString
传递其他编码
比如从C++层获取的字符数组是GB2312编码的,想要回传到Java层。
C++代码:
- 元素定位
jclass c = env->FindClass("java/lang/String");
javaString.class_ref = reinterpret_cast<jclass>(env->NewGlobalRef(c));
//java.lang.String的一个构造方法
javaString.constructor = env->GetMethodID(c, "<init>", "([BLjava/lang/String;)V");
env->DeleteLocalRef(c);
2.使用GB2312编码的字符数组,构造Java String
static jstring native_get_GB2312String(JNIEnv *env, jclass clazz) {
char c_str[5];
c_str[0] = 0xD6;//D6D0即"中"
c_str[1] = 0xD0;
c_str[2] = 0xB9;//B9FA即"国"
c_str[3] = 0xFA;
c_str[4] = 0x00;//即'\0',C语言里用来表示字符串的结尾
jbyteArray bytes = env->NewByteArray((jsize) strlen(c_str));
env->SetByteArrayRegion(bytes, 0, (jsize) strlen(c_str), (jbyte *) c_str);
jstring encoding = env->NewStringUTF("gb2312");
return reinterpret_cast<jstring>(env->NewObject(javaString.class_ref, javaString.constructor,
bytes, encoding));
}
注意事项:
- 这里使用了String的一个构造方法,构造方法的类型签名是
<init>
。 - 这里不是通过
AllocObject
方法创建String对象。而是通过JNI的NewObject
来调用String的特定构造方法。
测试代码:testGetGB2312