IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 【NDK】梳理JNI函数注册的方式以及时机 -> 正文阅读

[移动开发]【NDK】梳理JNI函数注册的方式以及时机

**

一、JNI函数注册方法以及时机图解

**
在这里插入图片描述

二、介绍

  1. 什么是 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* /* reserved */) {
    ...
    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
作者:彭丑丑

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-11 15:50:25  更:2021-12-11 15:52:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:28:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码