JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK开发指南之JNI动/静态注册全分析 在前面的章节里面我们主要讲解了JNI开发中的一些基本语法知识,并且在JNI/NDK开发指南之Eclipse集成NDK开发环境并使用中简单通过一个实例说明了NDK开发JNI流程。那么读者朋友们,是否有过这么一个疑问JNI中的虚拟机怎么知道我们使用Java中的Native方法时,具体调用到JNI Native中的那个函数呢。这个就是本篇.
JNI/NDK入门指南之JNI动/静态注册全分析
Android JNI/NDK入门指南目录
JNI/NDK入门指南之正确姿势了解JNI和NDK
JNI/NDK入门指南之JavaVM和JNIEnv
JNI/NDK入门指南之JNI数据类型,描述符详解
JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之javah和javap的使用和集成
JNI/NDK入门指南之Eclipse集成NDK开发环境并使用
JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK入门指南之JNI字符串处理
JNI/NDK入门指南之JNI访问数组
JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性
JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法
JNI/NDK入门指南之JNI异常处理
JNI/NDK入门指南之JNI多线程回调Java方法
JNI/NDK入门指南之正确姿势了解,使用,管理,缓存JNI引用
JNI/NDK入门指南之调用Java构造方法和父类实例方法
JNI/NDK入门指南之C/C++结构体和Java对象转换方式一
JNI/NDK入门指南之C/C++结构体和Java对象转换方式二
引言
在前面的章节里面我们主要讲解了JNI开发中的一些基本语法知识,并且在JNI/NDK开发指南之Eclipse集成NDK开发环境并使用中简单通过一个实例说明了NDK开发JNI流程。那么读者朋友们,是否有过这么一个疑问JNI中的虚拟机怎么知道我们使用Java中的Native方法时,具体调用到JNI Native中的那个函数呢。这个就是本篇的中点,下面让我为你娓娓道来。
一.JAVA JNI函数的查找原理
当我们在Java中调用Native方法时,我们一般都是通过JNI来实现的,我们只需要在java类中加载本地.so库文件,并声明native方法,然后在需要调用的地方调用即可,至于java中native方法的具体实现,全部交给了Native层。那么虚拟机是怎么知道该调用 so 中的哪个方法呢?这就需要用到注册的概念了,注册就是JNI中通过一定的机制建立Java中Native方法和本地代码中函数的一一对应关系,这种机制就是JNI的注册机制,通过这种机制就能找到Java中Native方法对应的JNI函数了。这种注册机制在日常中也比较常见了,譬如我们注册淘宝,微信啊,让后通过注册号的信息登录然后证明你是你。下面我们来分别介绍这两实现方式。如果读者你对JNI函数查找还有深入的了解的话,可以看看该篇文章JVM 查找 native 方法的规则。JNI函数的注册有两种方式,一种是静态注册方式,另一种是动态注册方式,且JNI的默认注册方式是静态的。
二.静态注册
闲话不多说,下面让我们分步骤介绍静态注册:
1.实现原理
根据Java函数名及包名来建立Java Native方法和JNI函数之间一一对应的关系。
2. 对应规则
(1) 对应的规则如下:
Java + 包名 + 类名 + 方法名
其中使用下划线将每部分隔开,包名也使用下划线隔开,如果名称中本来就包含下划线,将使用下划线加数字替换。
(2) 实例
这里我们以JNI/NDK开发指南之Eclipse集成NDK开发环境并使用的的demo为例来说明。其中JNI.java所在的包名是:com.xxx.jni,类名是:JNI,Native方法名是:helloNative。所以最后对应的JNI函数是:
package com.xxx.jni;
public class JNI {
public native void helloNative();
static {
System.loadLibrary("jni");
}
}
/*
* Class: com_xxx_jni_JNI
* Method: helloNative
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_xxx_jni_JNI_helloNative
(JNIEnv *, jobject);
3. 实现步骤
-
编写java代码和对应的Native方法
-
编译java代码,生成.class文件
-
用过javah指令,利用生成的.class文件生成JNI的.h文件
-
生成后的JNI头文件中包含了Java函数在JNI层的声明
4. 优点
一般说一个人都先说优点,那么说一个静态注册也是如下。它的优点只有一个就是:
简单明了。
4. 缺点
-
书写很不方便,因为JNI层函数的名字必须遵循特定的格式,且名字特别长
-
会导致程序员的工作量很大,因为必须为所有声明了native函数的java类编写JNI头文件;
-
程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时。
运行效率低,这个才是为啥不推荐使用静态注册的原因,因为使用JNI的绝大部分情况是为了提供效率。
三.动态注册
如果读者有阅读过一定的Android源码的化,此种注册是Android系统里面推荐的了。这个不是本文讨论的重点,就不在这里过多讨论了。
1.实现原理
直接通过 JNIEnv中提供的函数RegisterNatives方法手动完成 Java中Native 方法和JNI中相关函数的的绑定,这样虚拟机就可以通过这个函数映射表直接找到相应的方法了。
2. 实现步骤
-
利用结构体JNINativeMethod保存Java Native函数和JNI函数的对应关系;
-
在一个JNINativeMethod数组中保存所有native函数和JNI函数的对应关系;
-
在Java中通过System.loadLibrary加载完JNI动态库之后,调用JNI_OnLoad函数,开始动态注册;
-
JNI_OnLoad中调用通过调用JNIEnv中的函数RegisterNatives函数进行函数注册;
3. 具体实例
在这里我们依然使用前面的Java类和Native方法来演示:
(1) Java端代码
package com.xxx.jni;
public class JNI {
public native void helloNative();
static {
System.loadLibrary("jni");
}
}
(2) JNI端代码
#include "com_xxx_jni_JNI.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#define TAG "JNI"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
void native_helloNative(JNIEnv * env, jobject jobject){
LOGE(TAG,"This RegisterNatives fun\n");
}
/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java中用native关键字声明的函数名
2.签名(传进来参数类型和返回值类型的说明)
3.C/C++中对应函数的函数名(地址)
*/
static JNINativeMethod methods[] =
{
{ "helloNative", "()V",(void*) native_helloNative },
};
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* getMethods, int methodsNum) {
jclass clazz;
//找到声明native方法的类
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
if (env->RegisterNatives(clazz, getMethods, methodsNum) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env) {
//指定类的路径,通过FindClass 方法来找到对应的类
const char* className = "com/xxx/jni/JNI";
return registerNativeMethods(env, className, methods,
sizeof(methods) / sizeof(methods[0]));
}
//动态注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
//获取JNIEnv
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
//注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives
if (!registerNatives(env)) {
return -1;
}
//返回jni 的版本
return JNI_VERSION_1_6;
}
(3) 运行:
λ adb logcat -s JNI
--------- beginning of system
--------- beginning of main
12-18 19:31:51.560 8028 8028 I JNI : This RegisterNatives fun
如上就完成了JNI动态注册,并使用的过程。
3.优缺点
优点吗!就是弥补了,静态注册性能的不足。缺点吗,你也看到了比较繁琐。
4.RegisterNatives 方法解析
函数原型:
//C++
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
//C
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
jint);
参数分析:
- this:JNIEnv,这个在前面的篇章有介绍过
- clazz:指定的类,即 native 方法所属的类
- methods:方法数组,这里需要了解一下 JNINativeMethod 结构体
- nMethods:方法数组的长度
JNINativeMethod 结构体:
typedef struct {
const char* name;//java中用native关键字声明的函数名
const char* signature;//签名(传进来参数类型和返回值类型的说明),即对应的Native方法的签名
void* fnPtr;//C/C++中对应函数的函数名(地址)
} JNINativeMethod;
返回值:成功则返回 JNI_OK (0),失败则返回一个负值。
写在最后
好了,JNI中动态注册和静态注册就讲到这里了。聪明的你应该都已经将相关知识点get到了。好了,下期再见了。最后附上该章中所用测试代码JNI动态注册和静态注册演示。到这里,本章真的要结束了。青山不改绿水长流,各位江湖见!
更多推荐
所有评论(0)