一、JNI调用流程分析
1.Java层
示例代码:
1 2 3 4 5 6 7 8
| public class MediaScanner { static { System.loadLibrary("media_jni"); native_init(); } private static native void native_init(); }
|
2.JNI层
示例代码:
1 2 3 4
| //cpp static void android_media_MediaScanner_native_init(JNIEnv *env) { ... }
|
那么Java层调用native函数,系统是如何找到JNI层的对应函数的呢?
一、静态注册
流程:
- 编写java层native函数声明,并javac编译为class文件。
- 使用
javah -o output packagename.classname
生成.h头文件。
- JNI层cpp或者c文件实现该头文件。
当Java层第一次调用该native方法时,jvm会从对应库找对应的头文件。如果找到,保存这个函数指针供下次调用;如果没找到就抛异常。
弊端:
- 修改Java层的native方式后需要重新生成头文件,因为函数签名可能改变了。
- 初次调用native函数才会去建立关联,有懒加载特性,有利有弊。
二、动态注册
JNI中有一个数据结构专门来记录这种一一对应关系的。
1 2 3 4
| typedef struct { const char* signature; void* fnPtr; } JNINativeMethod;
|
AndroidRunTime.cpp提供了registerNativeMethods方法来注册;其中又通过JNIHelp.c的jniRegisterNativeMethods。
1 2
| jclass clazz = (*env) -> FindClass(env, className); (*env) -> RegisterNatives(env, clazz, gMethods, numMethods);
|
- 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.获取:
1 2 3
| //JNIEnv方法 jfieldID GetFieldID (jclass clazz, const char* name, const char* sig); jmethodID GetMethodID (jclass clazz, const char* name, const char* sig);
|
建议在初始化的时候获取并保存下来,提高下一次调用的运行效率。
2.使用:
1 2 3 4 5 6
| //JNIEnv方法 NativeType Call<type>Method (jobject jobj, jmethodID methodID, ...); NativeType CallStatic<type>Method (jmethodID methodID, ...); NativeType Get<type>Field (jobject jobj, jfieldID fieldID); void Set<type>Field (jobject jobj, jfieldID fieldID, NativeType value);
|
7.jstring
1.java层String与native层string的转换关系:
jstring
2.释放资源:
1 2 3
| //JNIEnv方法 void ReleaseStringChars(jstring str, char* chs); void ReleaseStringUTFChars(jstring str, char* chs);
|
转换字符串和释放资源方法应该是成对出现的。
8.JNI类型签名
命令:javap -s -p xxx.class
意义:
- -s 表示输出内部数据类型的签名信息。
- -p 表示打印所有函数和成员的签名信息。默认只打印public级别。
9.引用
类型:
1 2 3
| 1.Local Reference 本地变量 2.Global Reference 全局变量(强印用) 3.Weak Global Reference 弱全局变量
|
使用:
1 2 3 4 5 6 7
| //JNIEnv方法 NewGlobalRef(NativeType ref); NewWeakGlobalRef(NativeType ref); DeleteLoaclRef(NativeType ref); DeleteGlobalRef(NativeType ref); DeleteWeakGlobalRef(NativeType ref);
|
有时候我们可能创建太多LocalReference,这样内存占用可观,可以在方法结束调用前,提前手动回收LocalReference。
10.异常
JNI层可以截获和修改异常。
1 2 3 4
| //JNIEnv方法 ExceptionOccured 判断是否发生异常 ExceptionClear 清空当前JNI发生的异常 ThrowNew 向java层抛异常
|