JNI是Java Native Interface的缩写,意思是Java的本地接口,这个本地接口主要指Java可以通过本地接口去和其他的编程语言通信,有时在开发某个功能时想使用之前的技术积累或封装好的模块,但不幸的是之前不是用Java开发的,那对于此中情况该如何处理呢?对于经过时间验证的可靠程序不可能轻易重写和修改,所以就需要JNI作为程序的中转枢纽;
既然Jni是Java和其他语言的沟通桥梁,那么它既必须有一套基础协议作为与Java代码沟通的基础,这个基础就是类型的映射和签名,类型映射就是在Java类型和Jni中的类型建立一一对应关系,从而实现二者的类型可读性和唯一性,签名指Java中类型、方法、属性等在Java中的展现形式,根据最终的签名查找方法的对应关系;
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); 复制代码
//native public native void test(); //jni JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *, jobject); 复制代码
上面是Java代码中声明的test()转换后的Jni方法,此方法名称在编译javah文件时生成,在实现的C文件中重写并实现即可,方法的命名规则:
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 复制代码
静态注册JNI方法很简单,我们在Java中声明native方法后,会执行Java命令编译和生成Jni方法:
javac *** javah *** 复制代码
在执行javah的命令后,系统会在之间文件处创建.h文件,当我们在Java层调用native方法时,系统就会根据JNI方法命名规则,按照JNI方法名寻找对应的方法,在找到JNI方法后保存JNI指针建立方法间的联系,下次调用时直接使用函数指针就可以,不过静态注册在初次调用时需要查找再建立关联,影响效率,与静态注册对应的就是动态注册,不需要编译和查找关联;
JNIEXPORT jstring JNICALL native_method(JNIEnv *env, jobject) { return env->NewStringUTF("Register method in Jni"); }; 复制代码
static JNINativeMethod methods[] = { //参数:1、Java声明的native方法名;2、方法签名;3、c中实现的方法名 {"method", "()Ljava/lang/String;", (void *) native_method}, }; 复制代码
// 声明动态注册对应的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和关系数组实现方法的注册
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); //释放全局变量 } 复制代码
在介绍完JNI基础知识后,一起来学习下JNI在开发中的基本使用,也就是Jni的基本语法,其实在上面动态注册时已经使用到了一些,这里根据使用频率介绍下最常用的方法;
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->NewStringUTF(buff); // 字符串创建 } 复制代码
env->NewStringUTF(“this is string !"); 复制代码
const jchar *c_str = NULL; c_str = env->GetStringChars(j_str, ©); if (c_str == NULL) { return NULL; } sprintf(buff,"Jni %s",c_str); //将字符串缓存到buff中 env->ReleaseStringChars(j_str, c_str); 复制代码
jsize lenUtf = env->GetStringLength(j_str); 复制代码
jsize lenUtf = env->GetStringUTFLength(j_str); 复制代码
const jchar *c_str = NULL; char buff[128] = {}; jboolean copy; c_str = env->GetStringCritical(j_str, ©); //读取字符串 if (c_str == NULL) { return NULL; } sprintf(buff,"Jni %s",c_str); env->ReleaseStringCritical(j_str, c_str); //释放字符串 return env->NewStringUTF(buff); 复制代码
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest(JNIEnv *env, jobject cls, jstring j_str) { char buff[128] = {}; jsize lenUtf = env->GetStringUTFLength(j_str); //读取字符串长度 env->GetStringUTFRegion(j_str, 0, 4, buff); //截取字符串0~4缓存到buff中 return env->NewStringUTF(buff); //创建字符串 } //输入 "From Native !” ,输出结果:“From” 复制代码
Get<PrimitiveType>ArrayElements(ArrayType array, jboolean *isCopy) 复制代码
* 0:将 elems 内容回写到 Java 数组并释放 elems 占用的空间,回写的意思是会去修改原Java中的数组 * JNI_COMMIT:将 elems 内容回写到 Java 数组,但不释放 elems 的空间; * JNI_ABORT:不回写 elems 内容到 Java 数组,释放 elems 的空间。 复制代码
jint *array ; jboolean jboolean1; array = env->GetIntArrayElements(jintArray1, &jboolean1); //获取集合 if (array == NULL){ return; } array[2] = 5; //修改集合中的参数 env->ReleaseIntArrayElements(jintArray1,array,0); //释放array集合并写入Java集合 //在Java中调用Jni int[] number = new int[]{1,2,3,4,5}; Log.e("Before Jni=====",number[2] + ""); test.getAction(number); //调用Jni方法 Log.e("After Jni=====",number[2] + ""); 2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/Before Jni=====: 3 2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/After Jni=====: 5 //3改变程5 //使用JNI_ABORT 2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/Before Jni=====: 3 2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/After Jni=====: 3 //不变 复制代码
由上面的运行结果知道,在ReleaseIntArrayElements()传入0时,Java层传入的数组在执行方法后被改变,从而验证了将数据回写到Java的结论,在使用JNI_ABORT时,Java中数据并未发生改变;
jbyte* data = env->GetByteArrayElements(javaArray, NULL); if (data != NULL) { memcpy(buffer, data, len); env->ReleaseByteArrayElements(javaArray, data, JNI_ABORT); } 复制代码
env->GetByteArrayRegion(javaArray, 0, len, buffer); 复制代码
public class Action { public static void actionStatic(){ Log.e("=======","Static method from action in Java ") ; } } //Jni方法 JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *env, jobject) { jclass cls = NULL; jmethodID method_Id = NULL; cls = env->FindClass("com/alex/kotlin/jni/Action”); //查找Action类文件 method_Id = env->GetStaticMethodID(cls, "actionStatic", "()V”); //根据类、方法名、参数条件获取MethodId env->CallStaticVoidMethod(cls,method_Id); //调用静态方法 env->DeleteGlobalRef(cls); //释放class } 2019-04-27 17:25:27.580 10961-10961/com.alex.kotlin.jni E/=======: Static method from action in Java 复制代码
public class Action { public void action(){ Log.e("=======","Method from action in Java ") ; } } //Jni中代码 jclass clss = NULL; jmethodID method_Id = NULL; jmethodID construct_Id = NULL; jobject obj = NULL; clss = env->FindClass("com/alex/kotlin/jni/Action”); //查找Action类 construct_Id = env->GetMethodID(clss, "<init>", "()V”); //获取构造函数方法的Id obj = env->NewObject(clss, construct_Id); //创建构造Action实例 method_Id = env->GetMethodID(clss, "action", "()V”); //创建action()方法的Id env->CallVoidMethod(obj,method_Id); //调用action()方法 2019-04-27 17:42:31.774 11880-11880/com.alex.kotlin.jni E/=======: Method from action in Java 复制代码
public class Action { private static int number = 100; public int getNumber() { return number; } public void setNumber(int number) { Action.number = number; } } 复制代码
public native void getStaticInstance(); 复制代码
cls = env->FindClass("com/alex/kotlin/jni/Action”); //获取功能类 field_id = env->GetStaticFieldID(cls, "number", "I”); //获取静态变量Id number = env->GetStaticIntField(cls, field_id); //从类中获取静态变量 jint num = 555; env->SetStaticIntField(cls, field_id, num); //为静态变量赋值 复制代码
Action action = new Action(); action.setMessage("Message in Java"); action.setNumber(123); Log.e("Before======", action.getNumber() + ""); test.getStaticInstance(); Log.e("After Jni======", action.getNumber() + ""); 2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: 123 2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: 555 复制代码
public class Action { private String message = null; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } 复制代码
public native void getInstance(Action action); 复制代码
cls = env->GetObjectClass(obj); //从参数object中获取class field_id = env->GetFieldID(cls, "message", "Ljava/lang/String;”); //根据参数名和类型获取Field_Id str = static_cast<jstring>(env->GetObjectField(obj, field_id)); //根据field_Id从obj中获取实例 new_str = env->NewStringUTF("Message in Jni”); //创建新的String env->SetObjectField(obj, field_id, new_str); //设置Obj中的数值 复制代码
Log.e("Before======", action.getMessage() + ""); test.getInstance(action); Log.e("After Jni======", action.getMessage() + ""); 2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: Message in Java 2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: Message in Jni 复制代码
public class Action { public void action(){ Log.e("==========","Action in Action"); } } public class ChildAction extends Action { @Override public void action() { Log.e("==========","Action in ChildAction"); } } cls = env->FindClass("com.alex.kotlin.jni.Action”); //1、 jmethodID1 = env->GetMethodID(cls, "<init>", "()V”); //2、 jobject1 = env->NewObject(cls, jmethodID1); //3、 cls_parent = env->FindClass("com.alex.kotlin.jni.ChildAction”); //4、 jmethodID2 = env->GetMethodID(cls_parent, "action()", "()V”); //5、 env->CallNonvirtualVoidMethod(jobject1, cls_parent, jmethodID2); //6、 复制代码
static { System.loadLibrary("AccessCache"); initIDs(); } 复制代码
//Java 抛出异常代码 public void actionException() throws Exception { throw new Exception("Java 抛出异常"); } //C文件中调用 jmethodID1 = env->GetMethodID(cls, "actionException", "()V"); env->CallVoidMethod(obj, jmethodID1); if (env->ExceptionCheck()) { //检查并抛出异常 env->ExceptionDescribe(); env->ExceptionClear(); env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 抛出异常"); } //运行结果 2019-04-29 19:12:54.919 32350-32350/com.alex.kotlin.jni W/System.err: java.lang.Exception: Java 抛出异常 2019-04-29 19:12:54.920 32350-32350/com.alex.kotlin.jni E/AndroidRuntime: FATAL EXCEPTION: main Process: com.alex.kotlin.jni, PID: 32350 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alex.kotlin.jni/com.alex.kotlin.jni.MainActivity}: java.lang.Exception: Jni 抛出异常 复制代码
env->CallVoidMethod(obj, jmethodID1); if (env->ExceptionOccurred()) { env->ExceptionDescribe(); env->ExceptionClear(); env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 抛出异常"); } 复制代码
if ((*env)->ExceptionCheck(env)) { /* 异常检查 */ (*env)->ReleaseStringChars(env, jstr, cstr); // 发生异常后释放前面所分配的内存 return; } 复制代码
对于我们正常开发来说,直接使用Jni的场景很少,一般Jni的方法都会封装在So库中供Java层调用,现在就根据上面的Jni知识利用AS生成一个So库。
ndk.dir=/Users/wuliangliang/Library/Android/sdk/ndk-bundle 复制代码
cmake_minimum_required(VERSION 3.4.1) :配置So库的最小版本 add_library( //每个C文件具备一个add_library jni-lib // 配置So库名称 SHARED //设置So库设置为Shared共享库 test.cpp) // 源C文件的相对路径 find_library( //, log-lib log) target_link_libraries( jni-lib // 指定目标库 ${log-lib}) // 将目标库连接到NDK的日志库 复制代码
上面配置中:
android{ externalNativeBuild { cmake { path "src/main/jni/CMakeLists.txt” //配置CMakeList的文件路径 } } sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } } //配置Jni文件输出路径 } 复制代码
public class JniTest { static { System.loadLibrary("jni-lib”); //引入so库 } public static native String test(); //配置native方法 } 复制代码
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *env, jobject) { jclass cls = NULL; jmethodID method_Id = NULL; cls = env->FindClass("com/alex/kotlin/jni/Action"); if (cls == NULL) { return; } method_Id = env->GetStaticMethodID(cls, "test", "()V"); env->CallStaticVoidMethod(cls, method_Id); env->DeleteGlobalRef(cls); } 复制代码
这里调用FindClass()根据类名获取Class对象,然后使用全局变量保存Class对象,然后查找并调用actionStatic()
JniTest test = new JniTest(); tv.setText(test.test()); 复制代码
关于Jni的基本知识点和用法基本介绍完了,在一般的开发中可能使用的不多,但想做进一步的功能或优化时就经常会使用到,所以Jni也成为Android高级开发这必备基础知识,希望此篇文章的总结对需要的同学有所帮助;