java 在已有的so基础上封装jni_[干货]再见,Android JNI 封装
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 指针对象处理(nativejava)
4.8 超级复杂对象操作(nativejava)
4.9 静态成员方法访问(native->java)
4.10 Context 访问
4.11 异常处理
4.12 关于缓存
5 JNI 库一键构建框架
6 封装思路和开发工具
7 后续高级扩展
8 小结
9 参考资料
1 前言
最近名名
接到友邻团队的“求助”,临时调度帮助其 SDK 封装 JNI SDK,下面就用 SDK
和 JNI SDK
来区分这两个 SDK。以前我也不是搞这个的,但是因为干过一两次,多少有点经验,之前第一次封装后,我觉得这玩意可以总结成通用模板,但是因为本身对它不感兴趣,也就没去弄了,今天又来一次,我觉得有必要了,因为它就是个体力活
。今天总结这个模板
以及封装思路
,可以让我们快速的实现 JNI 封装。有如下这么些数据:
- 最后分解得到基础数据类,包括枚举体和通用数据体类总计:
40个
- 涉及 API 个数:
20个
- 涉及 API 复杂度:
- 有对象数组操作
- 有指针操作
- 最深三层类的嵌套
- 二层嵌套和三层嵌套的类占据 1/3 左右
- 涉及
Assets
资源文件操作
我自己挑战了一下,花了两天,按一天 8
小时工作量算(不包括吃饭、午睡),完成了:
- 依赖
SDK
的熟悉,毕竟需要了解流程,在 Java 层对接口形式做适度的优化 - Git 项目同步管理(很规矩的那种)
- SDK 数据结构分解到 Java 类
- 设计 Java APIs
- 编译框架(以前有 Native Headers 生成模板)
- 实现
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 内存结构
2 JNI 速查表
2.1 Java 和 Native 数据类型映射表
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 头文件里可以得知,想要啥,自己写个测试函数,然后生成一下就可以知道了。
2.2 引用类型
- jobject (all Java objects)
- jobjectArray (object arrays)
- jbooleanArray (boolean arrays)
- jbyteArray (byte arrays)
- jcharArray (char arrays)
- jshortArray (short arrays)
- jintArray (int arrays)
- jlongArray (long arrays)
- jfloatArray (float arrays)
- jdoubleArray (double arrays)
- jclass (java.lang.Class objects)
- jstring (java.lang.String objects)
- jarray (arrays)
- jthrowable (java.lang.Throwable objects)
想更清楚的了解的朋友,可以去 jni.h
和 jni_md.h
查看。
3 JNI 理论基础速览
- 「关于对象回收」:通俗点,对象只有一个,即在 Java 层 new 了,就不用在 Native 层再去 new;反之,要在 Native 层返回一个对象,则需要创建;Java 层内存是 JVM 自动管理的,Native 层,C/C++ 编写,你懂的。
- 「关于引用」:
- NewLocalRef:返回局部引用
- FindClass/GetObjectClass:返回局部引用(这两个函数作用一样,只是传入参数不一样)。
- NewObject:如果返回 Java 层继续引用,则局部引用不会被释放,如果是通过参数传递,赋值给参数,函数调用完毕就会释放。
- GetObjectClass:返回局部引用
- NewCharArray:返回局部应用
- ......
- 传递给 Native 方法的每个参数,以及 JNI 函数返回的几乎每个对象都属于局部引用,包括 jobject 及其所有子类。
- 局部引用仅在创建它们的线程中有效,不得将局部引用从一个线程传递到另一个线程。
jfieldID
和jmethodID
属于不透明类型,不是对象引用,因此总是可以缓存他们,以提升效率。而对于jclass
就需要注意了,得使用全局引用。
- 基本数据类型,如 int、char 之类的,在 Java 和 Native 层之间是直接拷贝一份,这个跟我们接触的传值、传引用是一样的。任何的 Java 对象都是通过引用传递的。
- 「局部引用」(Local Reference): 在函数返回后会被 JVM 自动释放掉,或者调用
(*env)->DeleteLocalRef(env, local_ref)
手动释放(「不管怎样」,尽量手动释放,防止局部引用表溢出,Android 8.0 上支持无限制的局部引用) - 「全局引用」(Global Reference): 调用
NewGlobalRef
,JVM 不会自动释放,基于局部引用创建,可跨方法、线程使用;必须调用(*env)->DeleteGlobalRef(env, g_ref);
手动释放。 - 「弱全局引用」(Weak Global Reference): 调用
NewWeakGlobalRef
基于局部引用或全局引用创建,可跨方法、线程使用;在 JVM 认为应该回收它的时候进行回收释放,或调用(*env)->DeleteWeakGlobalRef(env, g_ref)
手动释放;不同上面两种引用,不会阻止 GC 回收它引用的对象; - 引用比较:
(*env)->IsSameObject(env, obj1_ref, obj2_ref)
,判断引用对象(不分局部、全局、弱全局)是否相同。
- 「关于类」:我们都知道类有构造函数、实例、成员方法、成员变量。
- 「关于性能」:Native 层查找方法 ID、字段 ID、Class 引用效率是较低的(JVM 原因),因此可以基于这点在 Native 层做缓存优化。
- FindClass()
- GetFieldID()
- GetMethodId()
- GetStaticMethodID()
- GetIntField()
- 「关于缓存」:
JavaVM* vm
在整个进程中唯一
- 采用全局变量的方式缓存
- 静态局部变量缓存,直到程序结束才会释放;不加锁,多线程,存在多次缓存情况。
- 对局部引用进行静态变量缓存,会存在引用内容释放,成为野指针风险
- 全局变量缓存,声明定义
public static native
方法,到static {}
中调用,然后到 Native 层实现静态方法初始化相关全局变量,也可以实现缓存 - 返回基本类型的 Native 函数,不能造成全局引用、弱全局引用、局部引用的积累,即记得手动释放,防止造成内存溢出
- 返回引用类型的 Native 函数,除了要返回的引用之外,也不能造成任何的全局引用、弱全局引用、局部引用的积累
- 对于
jmethodID
和jfieldID
的缓存,是线程安全的。 jclass
需要结合NewGlobalRef
全局引用来实现缓存。jint JNI_OnLoad(JavaVM* vm, void* reserved){}
在System.loadLibary
加载本机代码后会自动调用;void JNI_OnUnload(JavaVM *vm, void *reserved){}
当 Classloader 销毁后会自动调用。
4 JNI 常用场景示例
4.1 字符串传递(java->native)
// 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);}
4.2 字符串返回(native->java)
// 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;}
4.3 数组传递(java->native)
4.3.1 基本类型数组
// 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 TEST_LOG_D("int array[%d] = %d", i, array[i]); } // 也可以使用 GetIntArrayRegion/GetPrimitiveArrayCritical 区别不在展开 env->ReleaseIntArrayElements(j_array, array, 0);
return getStatus(env, SUCCESS);}
4.3.2 对象类型数组
// 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 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);}
4.4 其他复杂对象传递(java->native)
// 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 // 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 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 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);}
4.5 复杂对象返回(native->java)
// 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 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;}
4.6 复杂数组对象返回(native->java)
4.6.1 基本类型二维数组
// 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 jint buff[256]; jintArray int_array = env->NewIntArray(col); if (int_array == NULL) { return NULL; } for (j = 0; j buff[j] = i + j; } env->SetIntArrayRegion(int_array, 0, col, buff); env->SetObjectArrayElement(result, i, int_array); env->DeleteLocalRef(int_array); }
return result;}
4.6.2 复杂对象数组
// public native ArrayList 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 // 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;}
4.7 指针对象处理(nativejava)
// 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);}
4.8 超级复杂对象操作(nativejava)
// 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 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 CStruct: If you created an object externally(Java Layer), you don't need to create it here. jobject j_struct = env->NewObject(cstruct_cache_header.clz, cstruct_cache_header.constructor); TEST_LOG_D("Create CStruct Successfully"); // Set values env->SetObjectField(j_struct, cstruct_cache_header.jid_point2d, j_point2d); env->SetBooleanField(j_struct, cstruct_cache_header.jid_ztype, JNI_FALSE); env->SetCharField(j_struct, cstruct_cache_header.jid_ctype, 'Y'); env->SetShortField(j_struct, cstruct_cache_header.jid_stype, 8); env->SetIntField(j_struct, cstruct_cache_header.jid_itype, 9); env->SetLongField(j_struct, cstruct_cache_header.jid_jtype, 10); env->SetFloatField(j_struct, cstruct_cache_header.jid_ftype, 11.0f); env->SetDoubleField(j_struct, cstruct_cache_header.jid_dtype, 12.0); env->SetObjectField(j_struct, cstruct_cache_header.jid_data, j_data); env->SetObjectField(j_struct, cstruct_cache_header.jid_inner_enum, j_inner_enum_one_obj); env->SetObjectField(j_struct, cstruct_cache_header.jid_innter_class, j_inner_class_obj); env->SetObjectField(j_struct, cstruct_cache_header.jid_rect, j_rect); env->SetObjectField(j_struct, cstruct_cache_header.jid_myrect, j_my_rect); env->SetObjectField(j_struct, cstruct_cache_header.jid_double_d_array, j_double_d_array);
return j_struct;}
// public native CommonStatus SetCStruct(CStruct data);JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetCStruct(JNIEnv *env, jobject, jobject j_struct) { if (!j_struct) { TEST_LOG_E("Input struct data is null."); return getStatus(env, FAILED); } // Note: The check of parameter boundary and function return value is omitted here!!! // Get byte[] data jbyteArray j_data_array = (jbyteArray)env->GetObjectField(j_struct, cstruct_cache_header.jid_data); if (NULL == j_data_array) { TEST_LOG_E("Failed to get object field."); return getStatus(env, FAILED); } jbyte *c_data = env->GetByteArrayElements(j_data_array, JNI_FALSE); // Get basic type value jboolean c_ztype = env->GetBooleanField(j_struct, cstruct_cache_header.jid_ztype); jchar c_ctype = env->GetCharField(j_struct, cstruct_cache_header.jid_ctype); jshort c_stype = env->GetShortField(j_struct, cstruct_cache_header.jid_stype); jint c_itype = env->GetIntField(j_struct, cstruct_cache_header.jid_itype); jlong c_jtype = env->GetLongField(j_struct, cstruct_cache_header.jid_jtype); jfloat c_ftype = env->GetFloatField(j_struct, cstruct_cache_header.jid_ftype); jdouble c_dtype = env->GetDoubleField(j_struct, cstruct_cache_header.jid_dtype); TEST_LOG_E("Get basic type value successfully"); // Get Point2D value jobject j_point2d = env->GetObjectField(j_struct, cstruct_cache_header.jid_point2d); jfloat c_point2d_x = env->GetFloatField(j_point2d, point2d.x); jfloat c_point2d_y = env->GetFloatField(j_point2d, point2d.y); TEST_LOG_E("Get Point2D value successfully"); // Get Rect value jobject j_rect = env->GetObjectField(j_struct, cstruct_cache_header.jid_rect); jint c_rect_left = env->GetIntField(j_rect, rect.left); jint c_rect_top = env->GetIntField(j_rect, rect.top); jint c_rect_right = env->GetIntField(j_rect, rect.right); jint c_rect_bottom = env->GetIntField(j_rect, rect.bottom); TEST_LOG_E("Get Rect value successfully"); // Get MyRect value jobject j_my_rect = env->GetObjectField(j_struct, cstruct_cache_header.jid_myrect); jobject j_my_rect_point2d_lefttop = env->GetObjectField(j_my_rect, my_rect.left_top); jobject j_my_rect_point2d_rightbottom = env->GetObjectField(j_my_rect, my_rect.right_bottom); jfloat c_my_rect_lefttop_x = env->GetFloatField(j_my_rect_point2d_lefttop, point2d.x); jfloat c_my_rect_lefttop_y = env->GetFloatField(j_my_rect_point2d_rightbottom, point2d.y); jfloat c_my_rect_rightbottom_x = env->GetFloatField(j_my_rect_point2d_rightbottom, point2d.x); jfloat c_my_rect_rightbottom_y = env->GetFloatField(j_my_rect_point2d_rightbottom, point2d.y); TEST_LOG_E("Get MyRect value successfully"); // Get inner enum type jint img_format_value = env->CallIntMethod(j_struct, cstruct_cache_header.inner_enum_md); TEST_LOG_E("Get Inner enum value successfully"); // Get inner class type jobject j_inner_class = env->GetObjectField(j_struct, cstruct_cache_header.jid_innter_class); jstring j_inner_class_msg = (jstring)env->GetObjectField(j_inner_class, cstruct_cache_header.innter_class_header.msg);
const char *c_str = env->GetStringUTFChars(j_inner_class_msg, 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); // Release byte[] env->ReleaseByteArrayElements(j_data_array, c_data, 0); return getStatus(env, SUCCESS);}
4.9 静态成员方法访问(native->java)
// public native void CallStaticMethod();JNIEXPORT void JNICALL Java_net_xiaobaiai_test_APIs_CallStaticMethod(JNIEnv *env, jobject) { jstring j_str = env->NewStringUTF("Hello Static Method"); env->CallStaticVoidMethod(static_method_header.clz, static_method_header.static_md, j_str, 100);
env->DeleteLocalRef(j_str);}
4.10 Context 访问
// public native CommonStatus SetContext(Context context);JNIEXPORT jobject JNICALL Java_net_xiaobaiai_test_APIs_SetContext(JNIEnv *env, jobject, jobject context) { jclass context_clz = env->GetObjectClass(context);
// get android application package name jmethodID m_getpackagename_id = env->GetMethodID(context_clz, "getPackageName", "()Ljava/lang/String;"); if (!context_clz || !m_getpackagename_id) { TEST_LOG_E("Failed to get class or method id"); return getStatus(env, FAILED); }
jstring j_pkg_name = static_cast(env->CallObjectMethod(context, m_getpackagename_id));if (!j_pkg_name) { TEST_LOG_E("Failed to call object method.");return getStatus(env, FAILED); }const char* pkg_name = env->GetStringUTFChars(j_pkg_name, 0);if (NULL == pkg_name) { TEST_LOG_E("Failed to get string UTF chars.");return getStatus(env, FAILED); } TEST_LOG_D("package name = %s", pkg_name); env->ReleaseStringUTFChars(j_pkg_name, pkg_name);return getStatus(env, SUCCESS);}
4.11 异常处理
- env->ExceptionOccurred
- env->ExceptionClear
在 native 层处理异常,这里就不展开了。
4.12 关于缓存
通过 jint JNI_OnLoad(JavaVM* vm, void* reserved
实现缓存初始化,void JNI_OnUnload(JavaVM *vm, void *reserved
实现缓存释放。缓存主要实现对 jclass
、jfieldID
、jmethodID
的缓存,具体可以参见:https://github.com/yicm/BazelMixedLanguage 代码实现。
5 JNI 库一键构建框架
- 支持 JNI Native 头文件自动生成
- 支持 JNI Library 生成
- 支持端到端,头文件生成->JNI 库生成
- 支持 Android APP 命令行编译(测试 JNI Library)
- 支持端到端,头文件生成->JNI 库生成->APK 生成
如果你很熟悉 JNI 的 Native 函数命名规则,可以直接根据 Java 类手撸 Native 层函数原型命名(我干不了)。有了这个框架,编译这一块也搞定了,效率杠杠的,具体可以参见开源项目:https://github.com/yicm/BazelMixedLanguage
6 封装思路和开发工具
思路:
- SDK 头文件拿到一梭子基本按照其数据结构类型编写 Java 层数据结构类型
- 设计 Java 层 APIs(需要了解 SDK 调用方式和输入输出)
- 直接利用框架开始编码、编译、调试、测试
涉及到开发工具如下:
- Vim + VS Code:代码编辑
- Bazel:编译
- adb:程序安装和调试
- Android Studio:辅助创建工程和代码编辑(可选)
7 后续高级扩展
- JNA
- https://github.com/java-native-access/jna
- 一个想法
- 直接通过 C 库头文件生成 Java 代码,效率再次提升,就不折腾了。
8 小结
文章内容有点多,如果完整的啃下来,JNI 这块就可以说 88 了。
本文涉及到了 JNI 封装中常用的模版,呕心沥血,看完这么多模板,你都能发现其中规律了,就那么几个操作。但是毕竟我只封装了三次,欢迎补充和指正,一起来提高开发效率。另外欢迎尝试 https://github.com/yicm/BazelMixedLanguage
,其中包含 Java 层代码,该开源项目绝对不会让你失望的。
9 参考资料
- https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
- https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp9502
- https://developer.android.google.cn/studio/profile/memory-profiler#jni-references
- https://developer.android.google.cn/training/articles/perf-jni
- https://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/performance.html
- https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#referencing_java_objects
- https://www.kancloud.cn/owenoranba/jni/120442
java 在已有的so基础上封装jni_[干货]再见,Android JNI 封装相关推荐
- java 在已有的so基础上封装jni_webshell中的分离免杀实践java篇
声明 由于传播.利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任. 雷神众测拥有对此文章的修改和解释权.如欲转载或传播此文章,必须保 ...
- WIFI时钟在已有硬件基础上的实现
WIFI时钟在已有硬件基础上的实现 暑假的小学期利用学校发的ESP8266-12f这块板子做了一个WIFI时钟,在最初做的时候发现有关ESP8266-12f资料相对来说还是比较少的,因此想要总结一下, ...
- python与anaconda安装(先安装了python后安装anaconda,基于python已存在的基础上安装anaconda)——逼死强迫症、超详解
目录 一.安装python(python3.7.4) 1.下载 (1)下载1(32位) (2)下载2(64位) 2.安装 3.配置python环境变量 4.检验python 二.安装anaconda( ...
- Java神操作之SpringBoot基础上添加Kotlin混合开发
第一步在原有基础上引入依赖: <dependency><groupId>org.jetbrains.kotlin</groupId><artifactId&g ...
- python与anaconda安装(先安装了python后安装anaconda,基于python已存在的基础上安装anaconda)
文章目录 前言 一.安装python 1.下载python的区别 二.安装anaconda(anaconda3) 1.下载 2.安装 3. 环境变量(在没有添加path选项的时候进行添加) 4.检验a ...
- 已有Win10基础上安装Ubuntu双系统(含BIOS操作)
参考:[Linux安装]Win10安装Ubuntu双系统(含BIOS操作)_百里飞洋的博客-CSDN博客_win10安装ubuntu双系统 一个16G的空U盘,台式机 U盘引导盘制作工具(Rufus) ...
- 【Scala教程】如何在已掌握的Java知识基础上快速上手Scala?【个人学习记录】
如何在已掌握的Java知识基础上学习Scala 前言 正文开始! 1. 编译运行 2. 声明变量 3. 输出 4. 显式指定变量类型 5. 范围区间 6. 写入文本文件 7. 读取文本文件 8. if ...
- c#快速入门~在java基础上,知道C#和JAVA 的不同即可
☺ 观看下文前提:如果你的主语言是java,现在想再学一门新语言C#,下文是在java基础上,对比和java的不同,快速上手C#,当然不是说学C#的前提是需要java,而是下文是从主语言是java的情 ...
- 八股文--Java 基础上
微信搜索公众号[程序员舒克],获取更多内容 目录 Java 基础上 Java概述 Java语言有哪些特点? Java和C++有什么关系,它们有什么区别? JVM.JRE和JDK的关系是什么? 什么是字 ...
最新文章
- 使用intellij idea制作可执行jar文件
- 工业富联2018年报来了!上市后首张成绩单大起底
- Python科学计算之Pandas基础学习
- Cisco路由器基础安全配置---特权模式和VTY线路密码
- Linux内核设计与实现---进程地址空间
- linux修图,修图只知道Photoshop?11款高逼格修图工具快来get!
- 邮箱不可用 550 User has no permission
- WordPress博客添加首页、文章页、页面、分类页、标签页的关键字和描述
- Kafka的消费语义
- ipv4网络配置方法
- [引]构造文法时表达式中算符优先级的问题
- VSTO 实现word的多级列表功能
- Hutool操作数据库的基本封装
- 放入http请求头中的内容如果含有中文必须编码
- 宝塔同时安装苹果cms海洋cms_海洋cms新手入门安装配置教程
- java商品类_编写java的一个商品类,急用,求大神帮忙,非常感谢
- TanDEM-X 90m数字高程模型(下载)
- CTypedPtrArray的引用
- Word 2016 撰写论文(3): 文献中常见的表格(三线表)制作
- Arndale Octa Board Exynos5420开发板之系统(Linaro Ubuntu)内核移植
热门文章
- oracle 邮件过程,oracle 发邮件 存储过程
- 【OS学习笔记】十八 保护模式五:保户模式下如何进行内存保护 与 别名段的意义与作用 对应汇编代码
- ElasticSearch初体验之使用Java进行最基本的增删改查
- Linux用户与“最小权限”原则
- sprintf_s与_snprintf与_snprintf_s
- treegrid.bootstrap使用说明
- 关于application title一直是untitled的问题
- 语言差异引起的问题解决一例
- leetcode之回溯backtracing专题4
- [Leedcode][JAVA][第139题][单词拆分][递归][记忆优化][动态规划]