Android总结-JNI原理及静态、动态注册
大家知道在JNI开发过程中,JAVA代码中调用SO动态库中的函数时,需要找到JAVA接口映射的Native函数。因此JNI就需要对JAVA代码中可能要访问的函数进行事先注册。目前有两种方法注册:静态注册和动态注册。
一、JNI概述
JNI(Java Native Interface,Java本地接口),用于打通Java层与Native(C/C++)层。这不是Android系统所独有的,而是Java所有。众所周知,Java语言是跨平台的语言,而这跨平台的背后都是依靠Java虚拟机,虚拟机采用C/C++编写,适配各个系统,通过JNI为上层Java提供各种服务,保证跨平台性。
二、JNI原理
Jni对于应用本身来说,可以看做一个代理模式。对于开发者来说,需要使用c/c++来实现一个代理程序(jni程序)来实际操作目标原生函数,java程序中则是jvm通过加载并调用此jni程序来间接地调用目标原生函数。
三、静态注册
静态注册的原理是先由JAVA代码编写需要调用的接口什么,然后通过JNI实现这些声明方法。
比较通用的做法是先创建一个java文件,声明需要使用JNI实现的接口,然后使用javah命令生成对应的C/C++头文件。再一一实现头文件中的函数即可。JAVA层调用JIN函数时,会从对应的JNI文件中查找该函数,因此需要把JAVA层接口和Native函数建立一层关联,静态注册的实现方法就是在Native函数命名上遵守特定的格式,否侧就会找不到对应函数而报错。
举个例子:
1. JAVA层接口声明
定义一个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 }
2.生成.h文件
声明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的参数类型。
3.定义.c/.cpp文件
剩下要做的就是在Native层实现.h定义的函数业务逻辑了。
JNI静态注册有一些缺点,如:
- native函数名称特别长,不利于书写
- 每次新增或删除接口时需要重新生成.h文件,比较繁琐
- 第一次调用时需要根据函数名建立索引,影响效率
- JNI层的函数名是由java接口名生成,很容易通过hook调用动态库中的函数。
四、动态注册
动态注册的原理是在JNI层通过重载JNI_OnLoad()函数来实现。
针对静态注册的缺点,动态注册方法就可以避免。动态注册的原理是通过RegisterNatives方法把C/C++函数映射到JAVA定义的方法,不需要通过JAVA方法名查找匹配Native函数名,也就不需要遵循静态注册的命名规则。
使用方法如下:
1.定义函数映射表
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;
属性说明:
- name:JAVA中的方法名称
- signature:描述JAVA方法的参数和返回值
- fnPtr:JAVA接口映射的Native函数指针。
其中signature字符串描述分为两部“()”中的表示方法参数类型,“()”后面表述返回值类型。数据类型分为两种:基本数据类型和对象类型:
基本数据类型:
对象类型:
示例:
“()V”表示void func()
“(I)V”表示void func(int param)
“(Ljava/lang/String;I)Ljava/lang/String;”表示String func(String param1, int param2)
2.重写注册入口函数
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层调用和静态注册中使用方法一样。
五、总结
以上哪里写的不对或者有待改进,欢迎大家提意见,谢谢!
转载请注明出处:http://www.luoxudong.com/?p=360
评论(1)
与v