转载自:JNI查找 native 方法的规则

通过上一篇文章,大家明白了调用 native 方法之前,首先要调用 System.loadLibrary 接口加载一个实现了native 方法的动态库才能正常访问,否则就会抛出 java.lang.UnsatisfiedLinkError 异常,找不到 XX 方法的提示。现在我们想想,在 Java 中调用某个 native 方法时,JVM 是通过什么方式,能正确的找到动态库中 C/C++ 实现的那个 native 函数呢?

JVM 查找 native 方法

JVM 查找 native 方法有两种方式:

  • 按照 JNI 规范的命名规则 (静态注册)
  • 调用 JNI 提供的 RegisterNatives 函数,将本地函数注册到 JVM 中。(动态注册,后面会详细介绍)

本文通过第一篇文章 HelloWorld 示例中的 Java_com_study_jnilearn_HelloWorld_sayHello 函数来详细介绍第一种方式:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_HelloWorld */  #ifndef _Included_com_study_jnilearn_HelloWorld
#define _Included_com_study_jnilearn_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/* * Class:     com_study_jnilearn_HelloWorld * Method:    sayHello * Signature: (Ljava/lang/String;)Ljava/lang/String; */
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello  (JNIEnv *, jclass, jstring);  #ifdef __cplusplus
}
#endif
#endif  

JNIEXPORT 和 JNICALL 的作用

在上篇文章中,我们在将 HelloWorld.c 编译成动态库的时候,用-I参数包含了 JDK 安装目录下的两个头文件目录:

gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin  

其中第一个目录为jni.h头文件所在目录,第二个是跨平台头文件目录(Mac os x系统下的目录名为 darwin,在 Windows 下目录名为 win32,linux 下目录名为 linux),用于定义与平台相关的宏,其中用于标识函数用途的两个宏 JNIEXPORT 和 JNICALL,就定义在 darwin 目录下的jni_md.h头文件中。在 Windows 中编译 dll 动态库规定,如果动态库中的函数要被外部调用,需要在函数声明中添加__declspec(dllexport)标识,表示将该函数导出在外部可以调用。在 Linux/Unix 系统中,这两个宏可以省略不加。这两个平台的区别是由于各自的编译器所产生的可执行文件格式不一样。这里有篇文章详细介绍了两个平台编译的动态库区别:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html。JNICALL 在 Windows 中的值为__stdcall,用于约束函数入栈顺序和堆栈清理的规则。

Windows 下jni_md.h头文件内容:

#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_  #define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
#define JNICALL __stdcall  typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;  #endif   

Linux 下jni_md.h头文件内容:

#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_  #define JNIEXPORT
#define JNIIMPORT
#define JNICALL  typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif  typedef signed char jbyte;  #endif  

从 Linux 下的jni_md.h头文件可以看出来,JNIEXPORT 和 JNICALL 是一个空定义,所以在 Linux 下 JNI 函数声明可以省略这两个宏。

函数的命名规则:

用 javah 工具生成函数原型的头文件,函数命名规则为:Java_类全路径_方法名。如Java_com_study_jnilearn_HelloWorld_sayHello,其中Java_是函数的前缀,com_study_jnilearn_HelloWorld是类名,sayHello是方法名,它们之间用 _(下划线) 连接。

函数参数:

JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(JNIEnv *, jclass, jstring);
  • 第一个参数:JNIEnv* 是定义任意 native 函数的第一个参数(包括调用 JNI 的 RegisterNatives 函数注册的函数),指向 JVM 函数表的指针,函数表中的每一个入口指向一个 JNI 函数,每个函数用于访问 JVM 中特定的数据结构。
  • 第二个参数:调用 Java 中 native 方法的实例或 Class 对象,如果这个 native 方法是实例方法,则该参数是 jobject,如果是静态方法,则是 jclass。
  • 第三个参数:Java 对应 JNI 中的数据类型,Java 中 String 类型对应 JNI 的 jstring 类型。(后面会详细介绍 JAVA 与 JNI 数据类型的映射关系)。

函数返回值类型:夹在 JNIEXPORT 和 JNICALL 宏中间的 jstring,表示函数的返回值类型,对应 Java 的String 类型。

总结:当我们熟悉了 JNI 的 native 函数命名规则之后,就可以不用通过javah命令去生成相应 java native方法的函数原型了,只需要按照函数命名规则编写相应的函数原型和实现即可。

比如com.study.jni.Utils类中还有一个计算加法的 native 实例方法add,有两个int参数和一个int返回值:public native int add(int num1, int num2),对应 JNI 的函数原型就是:JNIEXPORT jint JNICALL Java_com_study_jni_Utils_add(JNIEnv *, jobject, jint,jint)

转载自: Android深入理解JNI(一)JNI原理与静态、动态注册

前言

JNI不仅仅在NDK开发中应用,它更是Android系统中Java与Native交互的桥梁,不理解JNI的话,你就只能停留在Java Framework层。这一个系列我们来一起深入学习JNI。

1.JNI概述

Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。那为什么要这么划分呢?Android系统由Java写不好吗?除了性能的之外,最主要的原因就是在Java诞生之前,就有很多程序和库都是由Native语言写的,因此,重复利用这些Native语言编写的库是十分必要的,况且Native语言编写的库具有更好的性能。 
这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。 
 
通过JNI,Java世界的代码就可以访问Native世界的代码,同样的,Native世界的代码也可以访问Java世界的代码。 
为了讲解JNI我们需要分析系统的源码,在即将出版的《Android进阶之光》的最后一章中我拿MediaPlayer框架做了举例,这里换MediaRecorder框架来举例,它和MediaPlayer框架的调用过程十分类似。

2.MediaRecorder框架概述

MediaRecorder我们应该都不陌生,它用于录音和录像。这里不会主要介绍MediaRecorder框架,而是MediaRecorder框架中的JNI。 
 
Java世界对应的是MediaRecorder.java,也就是我们应用开发中直接调用的类。JNI层对用的是libmedia_jni.so,它是一个JNI的动态库。Native层对应的是libmedia.so,这个动态库完成了实际的调用的功能。

3.Java层的MediaRecorder

我们先来查看MediaRecorder.java的源码,截取部分和JNI有关的部分如下所示。 
frameworks/base/media/java/android/media/MediaRecorder.java

public class MediaRecorder{static {System.loadLibrary("media_jni");//1native_init();//2}
...   private static native final void native_init();//3
...public native void start() throws IllegalStateException;
...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在静态代码块中首先调用了注释1处的代码,用来加载名为“media_jni“的动态库,也就是libmedia_jni.so。接着调用注释2处的native_init方法,注释3处的native_init方法用native来修饰,说明它是一个native方法,表示由JNI来实现。MediaRecorder的start方法同样也是一个native方法。 
对于Java层来说只需要加载对应的JNI库,接着声明native方法就可以了,剩下的工作由JNI层来完成。

4.JNI层的MediaRecorder

MediaRecorder的JNI层由android_media_recorder.cpp实现,native方法native_init和start的JNI层实现如下所示。 
frameworks/base/media/jni/android_media_MediaRecorder.cpp

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{jclass clazz;clazz = env->FindClass("android/media/MediaRecorder");if (clazz == NULL) {return;}...fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");if (fields.post_event == NULL) {return;}
}static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{ALOGV("start");sp<MediaRecorder> mr = getMediaRecorder(env, thiz);process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

android_media_MediaRecorder_native_init方法是native_init方法在JNI层的实现,android_media_MediaRecorder_start方法则是start方法在JNI层的实现。那么,native_init方法是如何找到对应的android_media_MediaRecorder_native_init方法的呢? 
这就需要了解JNI方法注册的知识。

5.JNI方法注册

JNI方法注册分为静态注册和动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。

静态注册

在AS中新建一个Java Library名为media,这里仿照系统的MediaRecorder.java,写一个简单的MediaRecorder.java,如下所示。

package com.example;
public class MediaRecorder {static {System.loadLibrary("media_jni");native_init();}private static native final void native_init();public native void start() throws IllegalStateException;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

接着进入项目的media/src/main/java目录中执行如下命令:

javac com.example.MediaRecorder.java
javah com.example.MediaRecorder
  • 1
  • 2

第二个命令会在当前目录中(media/src/main/java)生成com_example_MediaRecorder.h文件,如下所示。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_MediaRecorder */#ifndef _Included_com_example_MediaRecorder
#define _Included_com_example_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     com_example_MediaRecorder* Method:    native_init* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_native_1init(JNIEnv *, jclass);//1/** Class:     com_example_MediaRecorder* Method:    start* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_start(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

native_init方法被声明为注释1处的方法,格式为Java_包名_类名_方法名,注释1处的方法名多了一个“l”,这是因为native_init方法有一个“_”,它会在转换为JNI方法时变成“_l”。 
其中JNIEnv * 是一个指向全部JNI方法的指针,该指针只在创建它的线程有效,不能跨线程传递。 
jclass是JNI的数据类型,对应Java的java.lang.Class实例。jobject同样也是JNI的数据类型,对应于Java的Object。关于JNIEnv * 以及JNI的数据类型会在本系列的后续文章中进行介绍。

当我们在Java中调用native_init方法时,就会从JNI中寻找Java_com_example_MediaRecorder_native_1init方法,如果没有就会报错,如果找到就会为native_init和Java_com_example_MediaRecorder_native_1init建立关联,其实是保存JNI的方法指针,这样再次调用native_init方法时就会直接使用这个方法指针就可以了。 
静态注册就是根据方法名,将Java方法和JNI方法建立关联,但是它有一些缺点:

  • JNI层的方法名称过长。
  • 声明Native方法的类需要用javah生成头文件。
  • 初次调用JIN方法时需要建立关联,影响效率。

我们知道,静态注册就是Java的Native方法通过方法指针来与JNI进行关联的,如果Native方法知道它在JNI中对应的方法指针,就可以避免上述的缺点,这就是动态注册。

动态注册

JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod,它在jni.h中被定义:

typedef struct {const char* name;//Java方法的名字const char* signature;//Java方法的签名信息void*       fnPtr;//JNI中对应的方法指针
} JNINativeMethod;
  • 1
  • 2
  • 3
  • 4
  • 5

系统的MediaRecorder采用的就是动态注册,我们来查看它的JNI层是怎么做的。 
frameworks/base/media/jni/android_media_MediaRecorder.cpp

static const JNINativeMethod gMethods[] = {
...{"start",            "()V",      (void *)android_media_MediaRecorder_start},//1{"stop",             "()V",      (void *)android_media_MediaRecorder_stop},{"pause",            "()V",      (void *)android_media_MediaRecorder_pause},{"resume",           "()V",      (void *)android_media_MediaRecorder_resume},{"native_reset",     "()V",      (void *)android_media_MediaRecorder_native_reset},{"release",          "()V",      (void *)android_media_MediaRecorder_release},{"native_init",      "()V",      (void *)android_media_MediaRecorder_native_init},...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上面定义了一个JNINativeMethod类型的gMethods数组,里面存储的就是MediaRecorder的Native方法与JNI层方法的对应关系,其中注释1处”start”是Java层的Native方法,它对应的JNI层的方法为android_media_MediaRecorder_start。”()V”是start方法的签名信息,关于Java方法的签名信息后续的文章会介绍。 
只定义JNINativeMethod 类型的数组是没有用的,还需要注册它,注册的方法为register_android_media_MediaRecorder: 
frameworks/base/media/jni/android_media_MediaRecorder.cpp

//JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{return AndroidRuntime::registerNativeMethods(env,"android/media/MediaRecorder", gMethods, NELEM(gMethods));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

register_android_media_MediaRecorder方法中return了AndroidRuntime的registerNativeMethods方法,如下所示。 
frameworks/base/core/jni/AndroidRuntime.cpp

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,const char* className, const JNINativeMethod* gMethods, int numMethods)
{return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
  • 1
  • 2
  • 3
  • 4
  • 5

registerNativeMethods方法中又return了jniRegisterNativeMethods方法: 
external/conscrypt/src/openjdk/native/JNIHelp.cpp

extern "C" int jniRegisterNativeMethods(JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods)
{...if (env->RegisterNatives(c.get(), gMethods, numMethods) < 0) {//1char* msg;(void)asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);env->FatalError(msg);}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

从注释1处可以看出,最终调用的JNIEnv的RegisterNatives方法,JNIEnv在JNI中十分重要,后续文章会介绍它。

register_android_media_MediaRecorder方法最终会调用JNIEnv的RegisterNatives方法,但是register_android_media_MediaRecorder方法是在哪被调用的呢?答案在register_android_media_MediaRecorder方法的注释上:JNI_OnLoad in android_media_MediaPlayer.cpp。这个JNI_OnLoad方法会在System.loadLibrary方法后调用,因为多媒体框架中的很多框架都要进行JNINativeMethod数组注册,因此,注册方法就被统一定义在android_media_MediaPlayer.cpp中的JNI_OnLoad方法中,如下所示。 
frameworks/base/media/jni/android_media_MediaPlayer.cpp

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {ALOGE("ERROR: GetEnv failed\n");goto *bail;}assert(env != NULL);...if (register_android_media_MediaPlayer(env) < 0) {ALOGE("ERROR: MediaPlayer native registration failed\n");goto *bail;}if (register_android_media_MediaRecorder(env) < 0) {//1ALOGE("ERROR: MediaRecorder native registration failed\n");goto *bail;}...result = JNI_VERSION_1_4;
bail:return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在JNI_OnLoad方法中调用了整个多媒体框架的注册JNINativeMethod数组的方法,注释1处的调用了register_android_media_MediaRecorder方法,同样的,MediaPlayer框架的注册JNINativeMethod数组的方法register_android_media_MediaPlayer也被调用了。

JNI查找 native 方法的规则(静态、动态注册)相关推荐

  1. JNI/NDK开发指南(二)——JVM查找java native方法的规则

    转载请注明出处:http://blog.csdn.net/xyang81/article/details/41854185 通过第一篇文章,大家明白了调用native方法之前,首先要调用System. ...

  2. JNI中Native方法的注册方式

    文章目录 JNI中native方法注册 native方法静态注册 native方法动态注册 数据类型转换 方法签名: 使用javap自动生成方法签名 JNIEnv JNI中native方法注册 jni ...

  3. JNI调用native方法出现 java.lang.UnsatisfiedLinkError: XXXclass.XXXmethod()异常的解决办法

    JNI调用native方法出现 java.lang.UnsatisfiedLinkError: XXXclass.XXXmethod()异常的解决办法 参考文章: (1)JNI调用native方法出现 ...

  4. Android NDK之静态/动态注册Native方法

    一.简介 关于NDK有两种方法注册:静态注册和动态注册. 静态注册: 就是直接在Java文件里写个native方法 然后再c/c++文件中实现这个方法就行了: 动态注册: 就是为了不要写很长的方法名, ...

  5. 图像拼接c语言,安卓上实现图像拼接(JNI调用NATIVE方法)

    [嵌牛导读]:安卓上使用摄像头获取图片,使用NATIVE的OPENCV方法进行图像拼接.文中的几个知识点:使用Intent调用系统默认相机拍摄照片:读取图片文件流转化为Bitmap:JNI中获取JAV ...

  6. 【学习Android NDK开发】Java通过JNI调用native方法

    参考:Android NDK,sample/hello-jni示例项目 系统环境:Ubuntu 12.04 LTS 32-bit 1.准备工作 打开Eclipse,新建Android项目: Appli ...

  7. Android之JNI动态注册native方法和JNI数据简单使用

    1.爆结果照片 2.介绍JNI注册方式 JVM 查找 native 方法有两种方式:     1).按照 JNI 规范的命名规则(静态注册)    2) .调用 JNI 提供的 RegisterNat ...

  8. JNI静态注册与动态注册

    导读 我们知道通过Java通过JNI可以调用C/C++代码,C/C++也可以通过JNI调用java代码,那么JNI是怎么将Java方法与Native的方法对应起来的呢? JVM查找Native方法有两 ...

  9. 【我的C语言学习进阶之旅】介绍一下NDK开发中关于JNI函数的两种注册方式:静态注册和动态注册

    目录 一.要介绍本篇博客的原因 二.静态注册 2.1 实现原理 2.2 实现过程 2.3 弊端 2.4 示例 三.动态注册 3.1 实现原理 3.2 实现过程 3.3 优点 3.4 示例 一.要介绍本 ...

最新文章

  1. VS Code关联vivado并安装Verilog插件
  2. Appium移动自动化框架原理
  3. 回顾亚马逊推荐系统20年发展
  4. 【译】Facebook 开源 Detectron
  5. linux dd命令参数及用法详解---用指定大小的块拷贝一个文件
  6. Linux 命令之 rm -- 删除文件和目录
  7. STM32F407之资源
  8. 小程序与UC浏览器打通
  9. Hbase常用基础命令
  10. 安装阿里Java代码规约插件
  11. 接外包项目的几点技巧
  12. 高三计算机专业vb试题二答案,高三计算机专业VB试题(二)
  13. qt界面之comboBox控件的三种基本用法
  14. JS Array转JSON
  15. 高项、高级项目管理师论文-人力资源管理
  16. Java学Web——day09【SQL多表联合查询】
  17. Python NLP 自然语言处理
  18. 分享几个好用的导航导航网站
  19. mm1排队系统仿真matlab实验报告,matlab仿真实验报告_mm1排队系统仿真matlab实验报告资料...
  20. OpenCV——line、circle、rectangle、ellipse、polylines函数的使用和绘制文本putText函数以及绘制中文的方法。

热门文章

  1. [答疑]调拨资金,需要双方账号。那么设置账号的用例,属于调拨资金用例的扩展么
  2. 继获得1100万美元投资后,Fincy将增招50位“战略型”人才
  3. 都在建设数据能力,爱数为什么强调全域?
  4. 区块链—未来的路还很长
  5. Hibernate分片
  6. Android通过手机的传感器计算手机的移动加速度
  7. 记一次docker虚拟机横向移动渗透测试
  8. 使用 nGlide 和 dgVoodoo2 玩经典 3DFX 游戏
  9. 拼单点外卖,AA付款
  10. 吉比特第三季营收13亿:靠“羊了个羊”走红 卢竑岩获分红3亿