1 前言
2 JNI 速查表
2.1 Java 和 Native 数据类型映射表
2.2 引用类型
3 JNI 理论基础速览
4 JNI 常用场景示例
4.1 字符串传递(java->native)
4.2 字符串返回(native->java)
4.3 数组传递(java->native)
4.4 其他复杂对象传递(java->native)
4.5 复杂对象返回(native->java)
4.6 复杂数组对象返回(native->java)
4.7 指针对象处理(native<-->java)
4.8 超级复杂对象操作(native<-->java)
4.9 静态成员方法访问(native->java)
4.10 Context 访问
4.11 异常处理
4.12 关于缓存
5 JNI 库一键构建框架
6 封装思路和开发工具
7 后续高级扩展
8 小结
9 参考资料
最近名名
接到友邻团队的“求助”,临时调度帮助其 SDK 封装 JNI SDK,下面就用 SDK
和 JNI SDK
来区分这两个 SDK。以前我也不是搞这个的,但是因为干过一两次,多少有点经验,之前第一次封装后,我觉得这玩意可以总结成通用模板,但是因为本身对它不感兴趣,也就没去弄了,今天又来一次,我觉得有必要了,因为它就是个体力活
。今天总结这个模板
以及封装思路
,可以让我们快速的实现 JNI 封装。有如下这么些数据:
40个
20个
Assets
资源文件操作我自己挑战了一下,花了两天,按一天 8
小时工作量算(不包括吃饭、午睡),完成了:
SDK
的熟悉,毕竟需要了解流程,在 Java 层对接口形式做适度的优化YAML
解析,我特意去找了个开源库,并对它实现了 Bazel
工程编译,使得 JNI SDK
直接依赖 Github
源码(爽歪歪)C++
和 Java
代码行数 2924
行 :$ find . -name "*.*" | xargs cat | grep -v -e ^$ -e ^\s*\/\/.*$ | wc -l
说了这么多,总之就是用了我的方法,两天内轻松完成了一个 JNI SDK 的封装。这次总结完成后,估计封装效率又会提升一截(写这文章前,我刚好弄完封装任务,剩下的时间就来总结了,希望同事看到了不要告状,不然你就看不到这篇呕心沥血
的文章分享了)。
不来虚的,直奔重点。对了,JNI 基础你需不需要呢?我觉得吧,看完这个,你都不用去了解 JNI 是个啥了,囫囵吞枣,依样画葫芦,直接照着干,ctrl-c\ctrl-v
,一梭到底(开玩笑开玩笑,多少还是要有点概念,至于这些就需要你自己去其他地方去了解了,包括 JNI 是啥、为啥要有 JNI ?、有啥利弊?、JNI Native 函数加载执行流程、JNI 静态/动态注册
、JNI 引用
、C/C++ 内存模型
、
、JVM GC 如何工作的等,额...)。Java 内存模型
、JVM 内存结构
Java 类型 | Native 类型 | 类型大小 | 符号 |
---|---|---|---|
boolean | jboolean / uint8_t | unsigned 8 bits | Z |
byte | jbyte / int8_t | signed 8 bits | B |
char | jchar / uint16_t | unsigned 16 bits | C |
short | jshort / int16_t | signed 16 bits | S |
int | jint / int32_t | signed 32 bits | I |
long | jlong / int64_t | signed 64 bits | J |
float | jfloat / float | 32 bits | F |
double | jdouble / double | 64 bits | D |
void | void | N/A | V |
Object | jobject | 引用对象大小,包括 jclass/jstring/jarray/jthrowable | Lfully/qualified/class/name; |
String | jstring / c++对象类 | N/A | Ljava/lang/String; |
Object[] | jobjectArray | N/A | N/A |
boolean[] | jbooleanArray | N/A | [Z |
byte[] | jbyteArray | N/A | [B |
char[] | jcharArray | N/A | [C |
short[] | jshortArray | N/A | [S |
int[] | jintArray | N/A | [I |
long[] | jlongArray | N/A | [J |
float[] | jfloatArray | N/A | [F |
double[] | jdoubleArray | N/A | [D |
函数 | N/A | public native long f (int n, String s, int[] arr); | (argument-types)return-type,比如(ILjava/lang/String;[I)J |
上面的东西你不知道,还有一个办法,就是去自动生成的 JNI 头文件里可以得知,想要啥,自己写个测试函数,然后生成一下就可以知道了。
想更清楚的了解的朋友,可以去 jni.h
和 jni_md.h
查看。
jfieldID
和 jmethodID
属于不透明类型,不是对象引用,因此总是可以缓存他们,以提升效率。而对于 jclass
就需要注意了,得使用全局引用。(*env)->DeleteLocalRef(env, local_ref)
手动释放( 「不管怎样」,尽量手动释放,防止局部引用表溢出,Android 8.0 上支持无限制的局部引用)NewGlobalRef
,JVM 不会自动释放,基于局部引用创建,可跨方法、线程使用;必须调用 (*env)->DeleteGlobalRef(env, g_ref);
手动释放。NewWeakGlobalRef
基于局部引用或全局引用创建,可跨方法、线程使用;在 JVM 认为应该回收它的时候进行回收释放,或调用 (*env)->DeleteWeakGlobalRef(env, g_ref)
手动释放;不同上面两种引用,不会阻止 GC 回收它引用的对象;(*env)->IsSameObject(env, obj1_ref, obj2_ref)
,判断引用对象(不分局部、全局、弱全局)是否相同。JavaVM* vm
在整个进程中唯一public static native
方法,到 static {}
中调用,然后到 Native 层实现静态方法初始化相关全局变量,也可以实现缓存jmethodID
和 jfieldID
的缓存,是线程安全的。jclass
需要结合 NewGlobalRef
全局引用来实现缓存。jint JNI_OnLoad(JavaVM* vm, void* reserved){}
在 System.loadLibary
加载本机代码后会自动调用; void JNI_OnUnload(JavaVM *vm, void *reserved){}
当 Classloader 销毁后会自动调用。// public native CommonStatus SetString(String str);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetString
(JNIEnv *env, jobject, jstring j_str) {
const char *c_str = NULL;
c_str = env->GetStringUTFChars(j_str, NULL);
if (NULL == c_str) {
TEST_LOG_E("Failed to get string UTF chars.");
return getStatus(env, FAILED);
}
TEST_LOG_D("c str: %s", c_str);
// 如使用 GetStringUTFRegion 与 GetStringRegion,则内部未分配内存,无需释放
env->ReleaseStringUTFChars(j_str, c_str);
return getStatus(env, SUCCESS);
}
// public native String GetString();
JNIEXPORT jstring JNICALL Java_net_xiaobaiai_test_APIs_GetString
(JNIEnv *env, jobject) {
char str[60] = "Hello";
// 1. 可以用 const char *
//const char *str = "Hello";
// 2. 可以用 std::string str = std::string("Hello"); str.c_str()
jstring result;
result = env->NewStringUTF(str);
return result;
}
// public native CommonStatus SetBaseTypeArray(int[] intArray);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetBaseTypeArray
(JNIEnv *env, jobject, jintArray j_array) {
// step: get length
int arr_len = env->GetArrayLength(j_array);
// step: get array
int * array = env->GetIntArrayElements(j_array, NULL);
if (!array) {
TEST_LOG_E("Failed to get int array elements");
return getStatus(env, FAILED);
}
for (int i = 0; i < arr_len; i++) {
TEST_LOG_D("int array[%d] = %d", i, array[i]);
}
// 也可以使用 GetIntArrayRegion/GetPrimitiveArrayCritical 区别不在展开
env->ReleaseIntArrayElements(j_array, array, 0);
return getStatus(env, SUCCESS);
}
// public native CommonStatus SetStringArray(String[] strArray);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetStringArray
(JNIEnv *env, jobject, jobjectArray j_str_array) {
// step1: get array length
int array_size = env->GetArrayLength(j_str_array);
// step2: get object array item with a loop
for (int i = 0; i < array_size; i++) {
jstring j_str = (jstring)(env->GetObjectArrayElement(j_str_array, i));
const char *c_str = env->GetStringUTFChars(j_str, NULL);
TEST_LOG_D("str array[%d] = %s", i, c_str);
env->ReleaseStringUTFChars(j_str, c_str);
}
return getStatus(env, SUCCESS);
}
// public native CommonStatus SetPoint2DArray(Point2D[] pointArray);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetPoint2DArray
(JNIEnv *env, jobject, jobjectArray j_array) {
// step1: get array length
int array_len = env->GetArrayLength(j_array);
// step2: get object array item with a loop
for (int i = 0; i < array_len; i++) {
// step2.1: get array element
jobject j_object = env->GetObjectArrayElement(j_array, i);
if (!j_object) {
TEST_LOG_E("Failed to get object array element");
return getStatus(env, FAILED);
}
// step2.2: get value
float x = env->GetFloatField(j_object, point2d.x);
float y = env->GetFloatField(j_object, point2d.y);
TEST_LOG_D("array[%d], x = %f, y = %f", i, x, y);
}
return getStatus(env, SUCCESS);
}
// public native CommonStatus SetPoint(PointF point);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetPoint
(JNIEnv *env, jobject, jobject j_pointf) {
// step2.2: get value
float x = env->GetFloatField(j_pointf, graphics_pointf.x);
float y = env->GetFloatField(j_pointf, graphics_pointf.y);
TEST_LOG_E("x = %f, y = %f", x, y);
return getStatus(env, SUCCESS);
}
// public native CommonStatus SetPointArrayList(ArrayList<PointF> array);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetPointArrayList
(JNIEnv *env, jobject, jobject j_point_array) {
int point_count = static_cast<int>(env->CallIntMethod(j_point_array, array_list.size));
if (point_count < 1) {
TEST_LOG_W("The array size less than 1");
return getStatus(env, FAILED);
}
double x, y;
for (int i = 0; i < point_count; ++i) {
jobject point = env->CallObjectMethod(j_point_array, array_list.get, i);
jfloat x = env->GetFloatField(point, graphics_pointf.x);
jfloat y = env->GetFloatField(point, graphics_pointf.y);
env->DeleteLocalRef(point);
TEST_LOG_D("x: %lf, y: %lf", x, y);
}
return getStatus(env, SUCCESS);
}
// public native String[] GetStringArray(int size);
JNIEXPORT jobjectArray JNICALL Java_net_xiaobaiai_test_APIs_GetStringArray
(JNIEnv *env, jobject, jint j_size) {
jobjectArray result;
result = (jobjectArray)env->NewObjectArray(j_size, env->FindClass("java/lang/String"), env->NewStringUTF(""));
if (!result) {
TEST_LOG_E("Failed to new object array");
return NULL;
}
for(int i = 0; i < j_size; i++) {
env->SetObjectArrayElement(result, i,
env->NewStringUTF((std::string("item ") + std::to_string(i)).c_str()));
}
return result;
}
// public native PointF GetPointf();
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_GetPointf
(JNIEnv *env, jobject j_obj) {
// The generated values are for testing only
jobject pt_object = env->NewObject(graphics_pointf.clz,
graphics_pointf.constructor, j_obj, 1.22f, 3.14f);
return pt_object;
}
// public native int[][] GetInt2DArray(int row, int col);
JNIEXPORT jobjectArray JNICALL Java_net_xiaobaiai_test_APIs_GetInt2DArray
(JNIEnv *env, jobject, jint row, jint col) {
jobjectArray result;
jclass cls_int_array;
jint i,j;
// step1: find class
cls_int_array = env->FindClass("[I");
if (cls_int_array == NULL) {
return NULL;
}
// step2: create int array object
result = env->NewObjectArray(row, cls_int_array, NULL);
if (result == NULL) {
return NULL;
}
// step3: set value
for (i = 0; i < row; ++i) {
jint buff[256];
jintArray int_array = env->NewIntArray(col);
if (int_array == NULL) {
return NULL;
}
for (j = 0; j < col; j++) {
buff[j] = i + j;
}
env->SetIntArrayRegion(int_array, 0, col, buff);
env->SetObjectArrayElement(result, i, int_array);
env->DeleteLocalRef(int_array);
}
return result;
}
// public native ArrayList<PointF> GetPointArrayList();
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_GetPointArrayList
(JNIEnv *env, jobject j_obj) {
const int array_size = 5;
jobject result = env->NewObject(array_list.clz, array_list.constructor, array_size);
for (int i = 0; i < array_size; i++) {
// step 1/2: new point
// The generated values are for testing only
jobject pt_object = env->NewObject(graphics_pointf.clz, graphics_pointf.constructor, j_obj, 0 + i, 1 + i);
// step 2/2: add point to array list
env->CallBooleanMethod(result, array_list.add, pt_object);
env->DeleteLocalRef(pt_object);
}
return result;
}
// public native CommonStatus InitHandle(PointHandle handle);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_InitHandle
(JNIEnv *env, jobject, jobject j_handle) {
CHandle* handle = (CHandle*)malloc(sizeof(CHandle));
if (!handle) {
TEST_LOG_E("Failed to new handle.");
return getStatus(env, FAILED);
}
CommonStatus status = InitCHandle(handle);
if (SUCCESS != status) {
TEST_LOG_E("Failed to init handle with %d.", status);
return getStatus(env, status);
}
jclass clz_handle = env->GetObjectClass(j_handle);
if (NULL == clz_handle) {
TEST_LOG_E("Failed to get handle object class.");
return getStatus(env, FAILED);
}
jfieldID p_handle = env->GetFieldID(clz_handle, "p_handle", "J");
if (NULL == p_handle) {
TEST_LOG_E("Failed to get handle pointer.");
return getStatus(env, FAILED);
}
TEST_LOG_E("handle value: %ld", (jlong)handle);
env->SetLongField(j_handle, p_handle, (jlong)handle);
return getStatus(env, SUCCESS);
}
// public native CommonStatus DestroyHandle(PointHandle handle);
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_DestroyHandle
(JNIEnv *env, jobject, jobject j_handle) {
jlong handle = getHandle(env, j_handle);
CHandle *p_handle = (CHandle*)handle;
if (!p_handle) {
TEST_LOG_E("Failed to get handle.");
return getStatus(env, FAILED);
}
CommonStatus status = DestroyCHandle(*p_handle);
if (SUCCESS != status) {
TEST_LOG_E("Failed to destroy handle with %d.", status);
return getStatus(env, FAILED);
}
free(p_handle);
p_handle = NULL;
return getStatus(env, SUCCESS);
}
// public native CStruct GetCStruct();
JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_GetCStruct
(JNIEnv *env, jobject) {
// Note: The check of parameter boundary and function return value is omitted here!!!
// Create Point2D
jobject j_point2d = env->NewObject(point2d.clz, point2d.constructor);
env->SetFloatField(j_point2d, point2d.x, 1.1f);
env->SetFloatField(j_point2d, point2d.y, 1.2f);
TEST_LOG_D("Create Point2d Successfully");
// Create Rect
jobject j_rect = env->NewObject(rect.clz, rect.constructor);
env->SetIntField(j_rect, rect.left, 1);
env->SetIntField(j_rect, rect.top, 2);
env->SetIntField(j_rect, rect.right, 3);
env->SetIntField(j_rect, rect.bottom, 4);
TEST_LOG_D("Create Rect Successfully");
// Create MyRect: Reuse the point that have been created
jobject j_my_rect = env->NewObject(my_rect.clz, my_rect.constructor);
env->SetObjectField(j_my_rect, my_rect.left_top, j_point2d);
env->SetObjectField(j_my_rect, my_rect.right_bottom, j_point2d);
TEST_LOG_D("Create MyRect Successfully");
// Create Inner Enum
jobject j_inner_enum_one_obj = env->GetStaticObjectField(cstruct_cache_header.inner_enum_header.clz,
cstruct_cache_header.inner_enum_header.jid_one);
// Create Inner Class
jobject j_inner_class_obj = env->NewObject(cstruct_cache_header.innter_class_header.clz, cstruct_cache_header.innter_class_header.constructor);
char c_msg[60] = "Hello";
jstring j_msg = env->NewStringUTF(c_msg);
env->SetObjectField(j_inner_class_obj, cstruct_cache_header.innter_class_header.msg, j_msg);
TEST_LOG_D("Create Inner Class Successfully");
// Create byte[]
const int c_data_len = 256;
jbyte c_data[c_data_len] = {‘b‘, ‘i‘, ‘a‘, ‘d‘, ‘a‘, ‘m‘, ‘m‘};
jbyteArray j_data = env->NewByteArray(c_data_len);
env->SetByteArrayRegion(j_data, 0, c_data_len, c_data);
TEST_LOG_D("Create Byte Array Successfully");
// Create 2d array
const int c_double_d_array_row = 10;
const int c_double_d_array_col = 5;
jclass double_d_clz = env->FindClass("[I");
jobjectArray j_double_d_array = env->NewObjectArray(c_double_d_array_row, double_d_clz, NULL);
for (int i = 0; i < c_double_d_array_row; i++) {
jintArray j_int_array = env->NewIntArray(c_double_d_array_col);
int c_int_array_data[c_double_d_array_col] = {1, 2, 3, 4, 5};
env->SetIntArrayRegion(j_int_array, 0, c_double_d_array_col, c_int_array_data);
env->SetObjectArrayElement(j_double_d_array, i, j_int_array);
}
TEST_LOG_D("Create 2d Array Successfully");
// Create