一、JNI调用流程分析
1.Java层
示例代码:
|
|
- 加载对应的JNI库。
- 声明native函数。
2.JNI层
示例代码:
|
|
那么Java层调用native函数,系统是如何找到JNI层的对应函数的呢?
一、静态注册
流程:
- 编写java层native函数声明,并javac编译为class文件。
- 使用
javah -o output packagename.classname
生成.h头文件。 - JNI层cpp或者c文件实现该头文件。
当Java层第一次调用该native方法时,jvm会从对应库找对应的头文件。如果找到,保存这个函数指针供下次调用;如果没找到就抛异常。
弊端:
- 修改Java层的native方式后需要重新生成头文件,因为函数签名可能改变了。
- 初次调用native函数才会去建立关联,有懒加载特性,有利有弊。
二、动态注册
JNI中有一个数据结构专门来记录这种一一对应关系的。
|
|
AndroidRunTime.cpp提供了registerNativeMethods方法来注册;其中又通过JNIHelp.c的jniRegisterNativeMethods。
|
|
- 1.当Java层通过System.loadLibrary加载so库时,会先找是否有JNI_OnLoad函数,如果有就调用它;
- 2.动态注册调用一般写在这个函数里。
提示:
Android提供了JNIHelp.h这个文件,它内部包含了jni.h,所以我们编写jni时可以直接引入JNIHelp.h。
4.什么是JNIEnv?
JNIEnv是一个与线程相关的代表JNI环境的结构体。我们经常通过它来从native层回调java层函数,但由于它是线程相关的,所以我们不能在B线程调用A线程的JNIEnv。
5.什么是JavaVM?
JavaVM顾名思义,就是一个java虚拟机变量,可以比作进程。不管有多少个线程,单进程就单独一个JavaVM。在JNI_OnLoad函数可以获得JavaVM。
- 调用JavaVM->AttachCurrentThread可以获得当前线程的JNIEnv,这样可以在子线程回调java层函数。
- 在线程结束时,调用JavaVM->DetachCurrentThread来释放相关资源。
6.什么是jfieldID和jmethodID?
1.获取:
|
|
建议在初始化的时候获取并保存下来,提高下一次调用的运行效率。
2.使用:
|
|
7.jstring
1.java层String与native层string的转换关系:
2.释放资源:
|
|
转换字符串和释放资源方法应该是成对出现的。
8.JNI类型签名
命令:javap -s -p xxx.class
意义:
- -s 表示输出内部数据类型的签名信息。
- -p 表示打印所有函数和成员的签名信息。默认只打印public级别。
9.引用
类型:
|
|
使用:
|
|
有时候我们可能创建太多LocalReference,这样内存占用可观,可以在方法结束调用前,提前手动回收LocalReference。
10.异常
JNI层可以截获和修改异常。
|
|