java 在已有的so基础上封装jni_[干货]再见,Android JNI 封装

5306a9f8cf75e18b32fd9c8d70d62bbe.png
  • 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,下面就用 SDKJNI 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++ 内存模型Java 内存模型JVM 内存结构、JVM GC 如何工作的等,额...)。

2 JNI 速查表

2.1 Java 和 Native 数据类型映射表

Java 类型Native 类型类型大小符号
booleanjboolean / uint8_tunsigned 8 bitsZ
bytejbyte / int8_tsigned 8 bitsB
charjchar / uint16_tunsigned 16 bitsC
shortjshort / int16_tsigned 16 bitsS
intjint / int32_tsigned 32 bitsI
longjlong / int64_tsigned 64 bitsJ
floatjfloat / float32 bitsF
doublejdouble / double64 bitsD
voidvoidN/AV
Objectjobject引用对象大小,包括 jclass/jstring/jarray/jthrowableLfully/qualified/class/name;
Stringjstring / c++对象类N/ALjava/lang/String;
Object[]jobjectArrayN/AN/A
boolean[]jbooleanArrayN/A[Z
byte[]jbyteArrayN/A[B
char[]jcharArrayN/A[C
short[]jshortArrayN/A[S
int[]jintArrayN/A[I
long[]jlongArrayN/A[J
float[]jfloatArrayN/A[F
double[]jdoubleArrayN/A[D
函数N/Apublic 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.hjni_md.h 查看。

3 JNI 理论基础速览

  • 「关于对象回收」:通俗点,对象只有一个,即在 Java 层 new 了,就不用在 Native 层再去 new;反之,要在 Native 层返回一个对象,则需要创建;Java 层内存是 JVM 自动管理的,Native 层,C/C++ 编写,你懂的。
  • 「关于引用」
    • NewLocalRef:返回局部引用
    • FindClass/GetObjectClass:返回局部引用(这两个函数作用一样,只是传入参数不一样)。
    • NewObject:如果返回 Java 层继续引用,则局部引用不会被释放,如果是通过参数传递,赋值给参数,函数调用完毕就会释放。
    • GetObjectClass:返回局部引用
    • NewCharArray:返回局部应用
    • ......
    • 传递给 Native 方法的每个参数,以及 JNI 函数返回的几乎每个对象都属于局部引用,包括 jobject 及其所有子类。
    • 局部引用仅在创建它们的线程中有效,不得将局部引用从一个线程传递到另一个线程。
    • jfieldIDjmethodID 属于不透明类型,不是对象引用,因此总是可以缓存他们,以提升效率。而对于 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 函数,除了要返回的引用之外,也不能造成任何的全局引用、弱全局引用、局部引用的积累
    • 对于 jmethodIDjfieldID 的缓存,是线程安全的。
    • 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 实现缓存释放。缓存主要实现对 jclassjfieldIDjmethodID 的缓存,具体可以参见: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 封装思路和开发工具

思路:

  1. SDK 头文件拿到一梭子基本按照其数据结构类型编写 Java 层数据结构类型
  2. 设计 Java 层 APIs(需要了解 SDK 调用方式和输入输出)
  3. 直接利用框架开始编码、编译、调试、测试

涉及到开发工具如下:

  • 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 层代码,该开源项目绝对不会让你失望的。

5d7c3b6a29f3e81f58dc12471092764a.png

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435236.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

神经网络与深度学习——TensorFlow2.0实战(笔记)(四)(python函数)

函数&#xff08;function) &#xff1a;实现某种特定功能的代码块 优点&#xff1a;程序简洁&#xff0c;可重复调用、封装性好、便于共享 类别&#xff1a;系统函数和用户自定义函数 Python内置函数 数学运算函数 print(abs(-1)) print(pow(2,3)) print(round(3.1415926,…

神经网络与深度学习——TensorFlow2.0实战(笔记)(四)(python模块、包和库)

模块&#xff08;Module&#xff09; 模块是一个python文件&#xff08;.py&#xff09;&#xff0c;拥有多个功能相近的函数或类。 便于代码复用&#xff0c;提高编程效率&#xff0c;提高了代码的可维护性。 避免函数名和变量名冲突。 包&#xff08;Package&#xff09;…

神经网络与深度学习——TensorFlow2.0实战(笔记)(四)(Python面向对象的编程)

面向对象的程序设计&#xff08;OOP&#xff09; 对象&#xff08;object&#xff09;: 将属性和方 法封装在一起。 类&#xff08;class)&#xff1a; 具有相同的属性和方法的对象集合。 对象是类的实例 子类继承了父类的全部属性和方法&#xff0c; 并且也有自己特有的属…

python3.8怎么打开创建_Python 3.8 新功能大揭秘【新手必学】

最新版本的Python发布了&#xff01;今年夏天&#xff0c;Python 3.8发布beta版本&#xff0c;在2019年10月14日&#xff0c;第一个正式版本已准备就绪。现在&#xff0c;我们都可以开始使用新功能并从最新改进中受益。 Python 3.8是Python语言的最新版本&#xff0c;它适合用于…

神经网络与深度学习——TensorFlow2.0实战(笔记)(四)(python文件)

路径 绝对路径&#xff1a;从盘符开始的路径 相对路径&#xff1a;从当前目录&#xff08;工作目录&#xff09;的路径 获取当前路径 #获取当前工作目录 import os print(os.getcwd())访问模式 文件对象open&#xff08;文件名&#xff0c;访问模式&#xff09; f open…

神经网络与深度学习——TensorFlow2.0实战(笔记)(四)(python异常处理)

异常&#xff1a; 程序运行时的错误&#xff0c;对应一个Python对象。 try: 语句块 except 异常1 as 错误原因: 出现异常1后的处理代码 except 异常2 as 错误原因: 出现异常2后的处理代码 #在程序运行时&#xff0c;解释器尝试执行try语句块中的所有代码。 try:alist [0,…

linux串口编程实例_Linux 网络编程——原始套接字实例:发送 UDP 数据包

以太网报文格式&#xff1a;IP 报文格式&#xff1a;UDP 报文格式&#xff1a;校验和函数&#xff1a;/*******************************************************功能&#xff1a;校验和函数参数&#xff1a;buf: 需要校验数据的首地址nword: 需要校验数据长度的一半返回值&am…

python2安装_如何安装python2

目前市面上python的主流版本有两个&#xff0c;一是python2.X的版本&#xff0c;另有一种是python3.X的版本。python2预计在2020年将停止维护&#xff0c;未来python3将是主流。 下面讲一下python2.x的安装详细讲解。Python安装&#xff1a; 本文以python 2.7.8(64位)为例说明&…

神经网络与深度学习——TensorFlow2.0实战(笔记)(四)(python上下文管理器)

with语句 使用with语句替代try-finally 语句&#xff0c;代码更加的简洁清晰 对于需要对资源进行访问的任务&#xff0c;无论在代码运行过程中&#xff0c;是否发 生异常&#xff0c;都会执行必要的清理操作&#xff0c;释放资源。 1. with open(r"D:\code1\pythontes…

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(NumPy科学计算库<1>python)

多维数组 形状&#xff08;Shape&#xff09;&#xff1a; 是一个元组&#xff0c;描述数组的维度&#xff0c;以及各个维度的长度。 长度&#xff08;Length&#xff09;&#xff1a; 某个维度中的元素个数。 数字单门课程成绩1*课程成绩一维数组多门课程成绩n*课程成绩二…

web扫描

随着网站越来越多元化&#xff0c;内容或资讯都会不定期更新&#xff0c;而每个新增的页面或连结&#xff0c;都有可能带来新的漏洞&#xff0c;因此&#xff0c;网站的安全性 检测不论在上线前或是每次更新时&#xff0c;都是务必检查的工作。 但是手动的网站检测&#xff0c;…

2.2基本算法之递归和自调用函数_数据结构与算法之5——队列和栈

栈和队列比较简单&#xff0c;而且实用性非常广泛&#xff0c;这里主要介绍一下他们的概念和实现&#xff0c;在很多算法中&#xff0c;栈和队列的运用很重要&#xff0c;因此&#xff0c;虽然简单确是最重要的数据结构之一&#xff0c;必须重视。栈是保证元素后进先出(后存入者…

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(NumPy科学计算库<2>python)

数组元素的切片 一维数组 #一维数组 #切片方法和Python序列数据结构的切片一样 anp.array([0,1,2,3,4],dtypenp.int64)#占用新的内存 #不包括结束位置 print(a[0:3]) print(a[:3]) print(a[0:]) 二维数组 #二维数组 anp.array([[0,1,2,3,4],[5,6,7,8,9],[10,11,12,13,14]],d…

c语言二进制数怎么表示_搞懂这些公式和原理,二级C语言对你来说肯定会简单很多!...

基本概念&#xff1a;机器数&#xff1a;在计算机中&#xff0c;一个数有二进制表示的数原码&#xff1a;第一位是符号位&#xff0c;其他位表示数值&#xff1a;0&#xff1a;正数&#xff0c;1&#xff1a;负数反码&#xff1a;正数-->原码&#xff0c;负数-->符号位不…

详细js读取execl内容并展示

作者execl内容展示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"> <!-- <script type"text/java…

python棋盘最短路径_Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例...

本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法)。分享给大家供大家参考&#xff0c;具体如下&#xff1a; # coding:utf-8 # Dijkstra算法——通过边实现松弛 # 指定一个点到其他各顶点的路径——单源最短路径 # 初始化图参数 G {1:{1:0, 2:1, 3:12}, 2:{2:…

js将百度坐标转为wgs84

作者execl示例 读取并转换结果如下 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- <scrip…

Jetty 服务器架构分析(中)

接上一篇&#xff0c;说到XmlConfiguration ,XmlConfiguration 利用自己实现的 IOC 组装 Server 的全过程如下图所示&#xff1a; 这里可以看到 3 个关键的配置文件&#xff0c; jetty.xml 、 jetty-deploy.xml 、以及 contexts/xxx.xml l Jetty.xml 文件中定义了…

VxWorks嵌入式操作系统的TrueFFS文件系统驱动开发

嵌入式系统对执行速度和系统可靠性的要求&#xff0c;决定了嵌入式系统需要一种安全、快速的存储设备&#xff0c;这种设备备同时还需要体积小、容量大、掉电数据不丢失等特点。而Flash存储器恰恰能够满足上述要求。这也使得Flash存储器成为嵌入式系统中的主要存储设备。 现…

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(NumPy科学计算库<矩阵和随机数>python)

矩阵和随机数 矩阵 创建矩阵 #创建矩阵 astring np.mat("1 2 3; 4 5 6") alist [[1,2,3],[4,5,6]] anplist np.array(alist) print(np.matrix(astring))#字符串、列表、元组、数组 print(np.mat(astring))#字符串、列表、元组、数组 print(np.mat(alist)) prin…