**
一、JNI函数注册方法以及时机图解
**
二、介绍
- 什么是 JNI 函数注册:
当 Java 虚拟机调用 native 方法时,需要调用对应的 JNI 函数,而 JNI 函数注册讨论的就是如何确定 natvie 方法与 JNI 函数之间的映射关系。
2.JNI函数注册的方式: 1)静态注册 2)动态注册
3.JNI函数注册方式的优缺点: 1)静态注册: 静态注册的优点是简单,因为静态注册采用的是基于约定的命名规则,所以可以通过 javah 或 IDE 自动生成函数声明。缺点是修改 Java 类名或方法名时,需要同步修改 JNI 函数命名。
2)动态注册: 动态注册的优点是灵活,因为动态注册可以自由定义 Java 方法和 JNI 函数命名的映射,当 Java 类名或方法名时只需要修改映射关系即可。缺点是牺牲了静态注册基于约定带来的便捷性。
三、具体实现
1、静态注册: 静态注册采用的是基于「约定」的命名规则,通过 javah 可以自动生成 native 方法对应的函数声明。例如:
HelloWorld.java
package com.xurui.hellojni;
public class HelloWorld {
public native void sayHi();
}
com_xurui_hellojni_HelloWorld.h
...
JNIEXPORT void JNICALL Java_com_xurui_hellojni_HelloWorld_sayHi
(JNIEnv *, jobject);
...
2)命名规则 静态注册的命名规则分为「无重载」和「有重载」两种情况:无重载时采用「短名称」规则,有重载时采用「长名称」规则。
短名称规则(short name)
1、前缀 Java_;
2、类的全限定名(带下划线分隔符_);
3、方法名
长名称规则(long name)
4、在短名称后追加两个下划线(__)和参数描述符
提示: 使用javap命令可以生成符合命名约定的头文件。
2.动态注册: 除了基于约定的静态注册外,还可以通过动态注册来确定 native 方法和 JNI 函数的映射关系。动态注册需要使用 RegisterNatives(…) 函数。
RegisterNatives(…) 函数: 一般会在JNI_Onload(…)函数中执行动态注册,例如:
android_media_MediaPlayer.cpp
jint JNI_OnLoad(JavaVM* vm, void* ) {
...
if (register_android_media_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
...
}
-> 2、调用 AndroidRuntime::registerNativeMethods
static int register_android_media_MediaPlayer(JNIEnv *env) {
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
1、native 方法与 JNI 方法的映射关系
static const JNINativeMethod gMethods[] = {
{
"nativeSetDataSource",
"(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
"[Ljava/lang/String;)V",
(void *)android_media_MediaPlayer_setDataSourceAndHeaders
},
{
"_setDataSource",
"(Ljava/io/FileDescriptor;JJ)V",
(void *)android_media_MediaPlayer_setDataSourceFD}
},
...
}
以上代码中,gMethods数组定义了 native 方法与 JNI 方法的映射关系,而调用 AndroidRuntime::registerNativeMethods 函数最终会调用RegisterNatives(…)函数,依次绑定 gMethods 数组中的每个映射关系。
JNIHelp.cpp
-> 调用 AndroidRuntime::registerNativeMethods
-> 最终调用的是:JNIHelp.cpp
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
scoped_local_ref<jclass> c(env, findClass(env, className));
3、关注点:调用 RegisterNatives
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
...
}
return 0;
}
其中,JNINativeMethod 是定义在jni.h中的一个结构体
typedef struct {
const char* name; native 方法名
const char* signature; native 方法的方法描述符
void* fnPtr; JNI 函数指针
} JNINativeMethod;
提示: 这里需要提醒下,很多资料都把 signature 说成是 JNI 这个知识下的概念,事实上它是 JVM 字节码中用于描述方法的字符串,是字节码中的概念。
四、注册 JNI 函数的时机
注册 JNI 函数的时机主要分为三种,这三种场景都是比较常见的:
注册的时机 | 对应的注册方式 |
---|
1、虚拟机第一次调用 native 方法时 | 静态注册 | – | – | 2、Android 虚拟机启动时 | 动态注册 | – | – | 3、加载 so 库时 | 动态注册 |
1、虚拟机第一次调用 native 方法时: 这种时机对应于静态注册,当虚拟机第一次调用该 native 方法时,会先搜索对应的 JNI 函数并注册。
2、Android 虚拟机启动时: 在 App 进程启动流程中,在创建虚拟机后会执行一次 JNI 函数注册。我们在很多 Framework 源码中可以看到 native 方法,但找不到调用 System.loadLibrary(…) 的地方,其实是因为在虚拟机启动时就已经注册完成了
AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {
...
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
}
...
}
start -> startReg:
int AndroidRuntime::startReg(JNIEnv* env) {
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
return 0;
}
startReg->register_jni_procs:
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env) {
for (size_t i = 0; i < count; i++) {
执行 JNI 注册
if (array[i].mProc(env) < 0) {
return -1;
}
}
return 0;
}
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
...
}
struct RegJNIRec {
int (*mProc)(JNIEnv*);
}
可以看到,Android 虚拟机启动时,会调用startReg()。其中会遍历调用gRegJNI数组,这个数组是一系列注册 JNI 函数的函数指针。
3、加载 so 库时: 在加载 so 库时,会回调JNI_Onload(…),因此这是注册 JNI 函数的好时候,例如上面提到的MediaPlayer也是在这个时候注册 JNI 函数。
五、总结
1、应理解注册 JNI 函数的两种方式:静态注册 & 动态注册; 2、应理解静态注册的函数命名约定、动态注册调用的RegisterNatives(…); 3、应知晓注册 JNI 函数的三个时机。
参考资料
- 《Android JNI 原理分析》 —— Gityuan 著
- 《JNI 编程指南》
来源:https://juejin.cn/post/6893157091633004558 作者:彭丑丑
|