1、Jni基础知识
JNI是Java Native Interface的缩写,意思是Java的本地接口,这个本地接口主要指Java可以通过本地接口去和其他的编程语言通信,有时在开发某个功能时想使用之前的技术积累或封装好的模块,但不幸的是之前不是用Java开发的,那对于此中情况该如何处理呢?对于经过时间验证的可靠程序不可能轻易重写和修改,所以就需要JNI作为程序的中转枢纽;
- Jni使用场景
- 需要调用Java语言不支持的依赖时,
- 整合非Java语言开发的系统,如C、C++
- 节省运行时间提高运行效率,如:音视频等
- Jni类型和Java类型的映射关系
既然Jni是Java和其他语言的沟通桥梁,那么它既必须有一套基础协议作为与Java代码沟通的基础,这个基础就是类型的映射和签名,类型映射就是在Java类型和Jni中的类型建立一一对应关系,从而实现二者的类型可读性和唯一性,签名指Java中类型、方法、属性等在Java中的展现形式,根据最终的签名查找方法的对应关系;
- native方法与Jni映射实例
public static native String action(short s , int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, ArrayList<String> array,
int[] arr, Action action);
//生成的Jni对应的代码
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_action
(JNIEnv *, jclass, jshort, jint, jlong, jfloat, jdouble, jchar, jboolean, jbyte, jstring, jobject, jobject, jintArray, jobject);
- 基本数据映射关系
- 引用类型关系映射表
- Jni方法名:Java_类全路径_方法名
//native
public native void test();
//jni
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *, jobject);
上面是Java代码中声明的test()转换后的Jni方法,此方法名称在编译javah文件时生成,在实现的C文件中重写并实现即可,方法的命名规则:
- Java:表示C++实现Java方法的前缀
- com_alex_kotlin_jni_JniTest:JniTest的类名全路径
- test:native方法名称
- 参数规则
- JNIEnv *:每个native函数的入口参数,执行JVM函数表的指针,JNIEnv即可在Native环境中使用Java资源
- jobject:调用java中native方法的实例或class对象,如果native方法是普通方法,则该参数是jobject,如果是静态方法,则是jclass
- 剩余参数为native方法的传入参数,此处为JNI中的映射类型(参照介绍映射关系)
- Jni签名
- 数据类型签名:见上面对照表
- 方法签名:将参数签名和返回值类型签名组合一起作为方法签名
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest
(JNIEnv *env, jobject cls, jstring j_str) {
}
方法签名:(Ljava/lang/Object,Ljava/lang/String)Lava/lang/String
1.1、 JNI 函数注册
- 静态注册
静态注册JNI方法很简单,我们在Java中声明native方法后,会执行Java命令编译和生成Jni方法:
javac ***
javah ***
在执行javah的命令后,系统会在之间文件处创建.h文件,当我们在Java层调用native方法时,系统就会根据JNI方法命名规则,按照JNI方法名寻找对应的方法,在找到JNI方法后保存JNI指针建立方法间的联系,下次调用时直接使用函数指针就可以,不过静态注册在初次调用时需要查找再建立关联,影响效率,与静态注册对应的就是动态注册,不需要编译和查找关联;
- 动态注册
- 在C++文件中实现native方法,此时方法名并没有严格要求
JNIEXPORT jstring JNICALL native_method(JNIEnv *env, jobject) {
return env->NewStringUTF("Register method in Jni");
};
- 创建注册的方法数组,在数组中建立Java方法和Jni方法的对应关系
static JNINativeMethod methods[] = {
//参数:1、Java声明的native方法名;2、方法签名;3、c中实现的方法名
{
"method", "()Ljava/lang/String;", (void *) native_method},
};
- 在重写的JNI_OnLoad()中调用注册方法实现注册
// 声明动态注册对应的Java类的路径
static const char *const PACKAGE_NAME = "com/alex/kotlin/jni/JniTest";
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env; //获取JNIEnv
if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6)) {
return JNI_ERR;
}
jclass jclass1 = env->FindClass(PACKAGE_NAME); //根据类名获取jclass
if (jclass1 == NULL) {
return JNI_ERR;
}
jclassGlobal = static_cast<jclass>(env->NewWeakGlobalRef(jclass1)); //创建全局缓存jclass
env->DeleteLocalRef(jclass1); //释放局部变量
if (JNI_OK != env->RegisterNatives(jclassGlobal, method, 1)) {
//注册方法
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
在创建的C++文件中重写Jni.h中的JNI_OnLoad()方法,在JNI_OnLoad中首先根据报名获取Java类的jclass对象,然后全局缓存jclass对象,最后调用RegisterNatives()方法传入jclass和关系数组实现方法的注册
- 在UnLoad()中解除方法注解
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
env->UnregisterNatives(jclassGlobal); //解除注册
env->DeleteGlobalRef(jclassGlobal); //释放全局变量
}
2、Jni基本使用
在介绍完JNI基础知识后,一起来学习下JNI在开发中的基本使用,也就是Jni的基本语法,其实在上面动态注册时已经使用到了一些,这里根据使用频率介绍下最常用的方法;
2.1、字符串使用
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest
(JNIEnv *env, jobject cls, jstring j_str) {
const char *c_str = NULL;
char buff[128] = {
};
jboolean copy;
c_str = env->GetStringUTFChars(j_str, ©); // 字符串访问
if(c_str == NULL){
return NULL;
}
sprintf(buff, "Jni %s", c_str); //字符串输出
env->ReleaseStringUTFChars(j_str, c_str); //字符串释放
return env-