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动态注册和静态注册演示。到这里,本章真的要结束了。青山不改绿水长流,各位江湖见!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐