大家知道在JNI开发过程中,JAVA代码中调用SO动态库中的函数时,需要找到JAVA接口映射的Native函数。因此JNI就需要对JAVA代码中可能要访问的函数进行事先注册。目前有两种方法注册:静态注册和动态注册。
JNI(Java Native Interface,Java本地接口),用于打通Java层与Native(C/C++)层。这不是Android系统所独有的,而是Java所有。众所周知,Java语言是跨平台的语言,而这跨平台的背后都是依靠Java虚拟机,虚拟机采用C/C++编写,适配各个系统,通过JNI为上层Java提供各种服务,保证跨平台性。
Jni对于应用本身来说,可以看做一个代理模式。对于开发者来说,需要使用c/c++来实现一个代理程序(jni程序)来实际操作目标原生函数,java程序中则是jvm通过加载并调用此jni程序来间接地调用目标原生函数。
静态注册的原理是先由JAVA代码编写需要调用的接口什么,然后通过JNI实现这些声明方法。
比较通用的做法是先创建一个java文件,声明需要使用JNI实现的接口,然后使用javah命令生成对应的C/C++头文件。再一一实现头文件中的函数即可。JAVA层调用JIN函数时,会从对应的JNI文件中查找该函数,因此需要把JAVA层接口和Native函数建立一层关联,静态注册的实现方法就是在Native函数命名上遵守特定的格式,否侧就会找不到对应函数而报错。
举个例子:
定义一个JniTest.java类,其中有两个方法。
package com.luoxudong;
public class JniTest {
static {
System.loadLibrary("TesJnitLib");//需要加载的动态库名称
}
public native int test1(int val);//测试接口1
public native String test2(String val);测试接口2
}
声明JAVA层接口以后,使用javah命令生成C/C++的都文件。
#include <jni.h>
#ifndef _Included_com_luoxudong_JniTest
#define _Included_com_luoxudong_JniTest
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_luoxudong_JniTest_test1
(JNIEnv *, jobject, jint);
JNIEXPORT jstring JNICALL Java_com_luoxudong_JniTest_test2
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
大家应该注意到了,生成的Native函数名称是有一定规则的:包名_类名_方法名称,其中JAVA包名中的“.”替换成“_”了。参数对应的JAVA参数类型换成了对应JNI的参数类型。
剩下要做的就是在Native层实现.h定义的函数业务逻辑了。
JNI静态注册有一些缺点,如:
动态注册的原理是在JNI层通过重载JNI_OnLoad()函数来实现。
针对静态注册的缺点,动态注册方法就可以避免。动态注册的原理是通过RegisterNatives方法把C/C++函数映射到JAVA定义的方法,不需要通过JAVA方法名查找匹配Native函数名,也就不需要遵循静态注册的命名规则。
使用方法如下:
static JNINativeMethod method_table[] = {
{"test1", "(I)I", (void *)n_test1},
{"test2", "(Ljava/lang/String;)Ljava/lang/String;", (void *)n_test2}
};
JNINativeMethod的结构体定义为
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
属性说明:
其中signature字符串描述分为两部“()”中的表示方法参数类型,“()”后面表述返回值类型。数据类型分为两种:基本数据类型和对象类型:
示例:
“ ( ) V ”表示void func()
“ (I)V ”表示void func(int param)
“(Ljava/lang/String;I)Ljava/lang/String;”表示String func(String param1, int param2)
so加载时会先调用JNI_OnLoad函数,重写该函数,在里面实现动态注册JNI方法。
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK)
{
return -1;
}
if (!registerNatives(env)) //注册
{
return -1;
}
result = JNI_VERSION_1_4;
return result;
}
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, method_table, sizeof(method_table) / sizeof(method_table[0])))
{
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL)
{
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0)
{
return JNI_FALSE;
}
return JNI_TRUE;
}
这样函数动态注册就完成了,JAVA层调用和静态注册中使用方法一样。