JNI基础(3): Java中调用JNI方法
3.1 Java中加载动态库
java中通过系统提供的如下方法来加载动态库。
static {
System.loadLibrary("cmakecompile");
}
ps: java中的static代码块随着类的加载而执行,并且只会执行一次,并且还优先于main函数。
其实只需要在调用JNI方法之前load就行,可以捕获一下异常来检查是否加载成功,加载不成功就不能调用native方法 否则肯定会出现找不到方法而闪退。
try {
System.loadLibrary("cmakecompile");
} catch (Throwable e) {
e.printStackTrace();
}
3.2 JNI中的JNIEnv和JavaVM的关系
-
JavaVM:代表Java虚拟机。所有的方法调用从这里开始。
- 可以通过实现
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
方法来获取JavaVM的指针。 - 也可以用
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
来主动创建JavaVM。但此方法在安卓上没有导出所以没法调用,先忽略。
- 可以通过实现
-
JNIEnv:是提供各个JNI Native函数的结构体跟线程相关,不同线程之前的JNIEnv相互独立。但同一个线程中的
JNIEnv*
是一样的。如果不能确保方法都在同一个线程中调用,就不能把这个JNIEnv* 存起来。
两者中JavaVM是在各个线程中共享。android只允许有一个JavaVM。在不同线程中需要JNIEnv*时可以通过JavaVM获取。所以可以考虑全局存储JavaVM。 Java 的dex字节码和c/c++的.so同时运行Dalvik虚拟机之内,共同使用一个进程空间。之所以可以相互调用,也是因为有Dalvik虚拟机。
3.3 Java中调用Native方法方式,命名规则和JNI头文件创建方式
java调用native方法 首先需要用native关键字声明java方法。例如:
public native String intFromJNI();
此时Android Studio会提示找不到这个方法的JNI实现,我们点击提示的创建该方法的JNI实现
选择实现文件就会自动添加进去,然后我们去实现相关的C++实现即可。先看看自动添加的代码:
extern "C" JNIEXPORT jstring JNICALL Java_com_memetghini_cmakecompile_MainActivity_intFromJNI(JNIEnv *env, jobject this) {
}
首先extern "C"
是因为c/c++编绎器对函数名译码的方式不同所引起的。c语言的函数是通过函数名来识别的,但c++有方法重载等问题所以函数编译后生成的符号里面体现了函数的名字,参数类型,返回值类型等信息,产物就不一样。所以就算是一样的代码通过c和c++编译器编译之后连接方式就不太一样。所以这里extern "C"是为了避免c++编译按照c++的方式编译c函数,这里jni函数就是一个c函数。可以通过用__cplusplus宏来包围的形式同时支持c编译来编译是忽略这个extern c,因为c编译压根就不认识这个。
#ifdef __cplusplus
extern "C" {
#endif
jni methods
#ifdef __cplusplus
}
#endif
JNIEXPORT
显然就是要导出此方法。Windows平台中需要导出动态库的方法需要#define JNIEXPORT __declspec(dllexport)
。如果此方法不在导出的符号表中JNI也无法调用它。例如在mac平台下可以不加,默认会导出。如果替换为__attribute__ ((visibility ("hidden"))) jint JNICALL
就找不到了。
JNICALL
在windows中的值为__stdcall,用于约束函数入栈顺序和堆栈清理的规则。在linux,mac平台下是一个空声明。所以简单来说在Linux平台上这两个宏不加也没有影响。
方法命名规则是:Java开头然后拼接包名再拼接类名最后是方法名,每一个都用下划线_
连接即可。默认带两个参数。
- JNIEnv* 是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。简单来说在JNI中获取Java的类和相关方法都得通过这个JNIEnv*指针来获取。
- 调用java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是jobject,如果是静态方法,则是jclass。
- 在后面就是方法本身的参数,上面的方法目前是没有参数的所以为空。