jni两种注册过程实战(代码片段)

渡口一艘船 渡口一艘船     2022-12-07     766

关键词:

JNI系列

JNI两种注册过程实战

深入理解JNI

概述

Android OS加载JNI Lib的方法有两种
- JNI_OnLoad(动态注册)
- 如果JNI Lib实现中没有定义JNI_OnLoad,则dvm调用dvm ResolveNativeMethod进行动态解析(静态注册)
因此,当 java 通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会调用 JNI_OnLoad 的函数。
本文主要介绍JNI实战中的两种方式(静态注册和动态注册)并比较二者的优劣和实战中的一些坑

0x00 静态注册

静态注册基本原理

根据函数名来建立java方法和JNI函数间的一一对应关系。

静态有两个非常重要的关键字JNIEXPORTJNICALL,这两个关键字时宏定义,主要用于说明该函数是JNI函数,在虚拟机加载so库时,如果发现函数含有上面两个宏定义时,就会链接到对应java层的native方法。

如何使用静态注册

主要是几个关键点

  • 先写java代码,编译生成class文件
  • 使用 javah -jni 包名.文件名 (按照网上使用的方式不好使,在java目录下成功得到.h文件)
  • 配置文件

1 gradle.progerties文件下添加如下代码

android.useDeprecatedNdk=true

这里要说明一下,构建jni模块工程有3种方式

  • 最古老的方式 (非必要)【不使用gralde+手动生成文件+手动ndk-build】

手动写 Android.mk、Applicatoin.mk ,然后手动调用ndk-build去生成so包,早期在ADT时代开发常用

  • 最前沿的方式 使用gradle-experimental 【使用gradle+全自动生成】
    这表示使用当前版本 Gradle 插件,继续使用过时的NDK。由于google为NDK开发提供了一个实验性的工具gradle-experimental但截止目前为止最新版本为0.7.3,从版本号上来看目前仍在实验性,而且需要替换project和app的graldebuilde.gradle,需要改写app的gradle,使用model的方式,改动还是比较大的,但是带来了很大方便,可以直接创建对应的h文件,参考文献[2-4]介绍了这种最新的方式

  • 折中的方式【使用gradle+手动生成头文件+自动ndk-build】

这种方便对当前gradle无改变,唯一需要的是手动生成JNI的头文件(也可以通过配置external tool来自动生成[5])就可以。本文就是采取了这种方式来构建自己的Jni,而自动构建ndk-build就是交给了android.useDeprecatedNdk=true,表示使用ndk来build(因为谷歌出了gradle-experimental更推荐这种方式,但是这种方式改动较大、也不稳定)。

2 build.gradle文件在defaultConfig文件中

        //这里是配置ndk
         ndk
            moduleName "xsfJni"
            ldLibs "log" //添加依赖库,因为有log等打印
            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。
        

3 写.c文件(注意与.h文件相同),并且在natvie方法中加载工具类

    //加载静态库
    static 
        System.loadLibrary("Test");//此处加载的是相应的模块库,名称必须和 ndk的moduleName名一样。
    

在这里使用了以前的,可以看下定义的Util类和使用javah -jni生成的h头文件

public class NDKUtils 
    static 
        System.loadLibrary("xsfJni");   //defaultConfig.ndk.moduleName
    
    //测试静态方法Jni
    public native String  getVipString();
    public native String  generateKey(String name);

通过在 java文件夹下执行 javah -jni xsf.jnidemo.NDKUtils 得到h文件(当然你也可以自己手搓一个,只是容易写错而已),截取其中部分生成代码

#include <jni.h>

#ifndef _Included_xsf_jnidemo_NDKUtils
#define _Included_xsf_jnidemo_NDKUtils
#ifdef __cplusplus
extern "C" 
#endif

JNIEXPORT jstring JNICALL Java_xsf_jnidemo_NDKUtils_getVipString
  (JNIEnv *, jobject);
JNIEXPORT jstring JNICALL Java_xsf_jnidemo_NDKUtils_generateKey
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus

#endif
#endif

可以看出JNI调用函数名称是按照一定的规则去生成的,规则如下:

java_完整包名_类名_方法名

静态注册弊端:

  • 后期类名、文件名改动,头文件所有函数将失效,需要手动改,超级麻烦易出错

  • 代码编写不方便,由于JNI层函数的名字必须遵循特定的格式,且名字特别长;

  • 会导致程序员的工作量很大,因为必须为所有声明了native函数的java类编写JNI头文件;

  • 程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时。

0x01动态注册

静态注册JNI弊端多多,因此使用动态注册JNI十分有必要。

动态注册的原理

直接告诉native函数其在JNI中对应函数的指针;

动态注册的原理是这样的:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数,
而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)。

实现过程:

  • 利用结构体JNINativeMethod保存Java Native函数和JNI函数的对应关系;

  • 在一个JNINativeMethod数组中保存所有native函数和JNI函数的对应关系;

  • 在Java中通过System.loadLibrary加载完JNI动态库之后,调用JNI_OnLoad函数,开始动态注册;

  • JNI_OnLoad中会调用AndroidRuntime::registerNativeMethods函数进行函数注册;

  • AndroidRuntime::registerNativeMethods中最终调用jniRegisterNativeMethods完成注册。

如何使用动态注册

在NDKUtils中添加一个动态使用JNI的方法

//测试动态方法Jni
public native String  dynamicGenerateKey(String name);

函数映射表

JNINativeMethod这其实是一个结构体,在jni.h头文件中定义,通过这个结构体从而使Java与jni建立联系

typedef struct 

const char* name; //Java中函数的名字

const char* signature;//符号签名,描述了函数的参数和返回值

void* fnPtr;//函数指针,指向一个被调用的函数

 JNINativeMethod;

关于签名符号可以参考我之前的总结http://blog.csdn.net/xsf50717/article/details/51598748

回到正题,在我们创建的C文件中,上面的dynamicGenerateKey函数映射表如下

//函数映射表
static JNINativeMethod methods[] = 
        "dynamicGenerateKey", "(Ljava/lang/String;)Ljava/lang/String;", (void *) native_dynamic_key,
        //这里可以有很多其他映射函数
;

JNI_OnLoad()函数

在开篇我们就提过当 java 通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会调用 JNI_OnLoad 的函数。这个函数主要有两个作用

  • 指定Jni版本

告诉JVM该组件使用哪一个jni版本(若未提供JNI_Onload函数,JVM会默认使用最老的JNI1.1版本),如果要使用新的版本的JNI,如JNI 1.4版本,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中)来告知JVM
- 初始化

当JVM执行到System.loadLibrary() 函数时,会立即调用 JNI_OnLoad() 方法,因此在该方法中进行各种资源的初始化操作最为恰当,

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)

    LOGD("-------------JNI_OnLoad into.--------\\n");
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK)
    
        return -1;
    
    assert(env != NULL);
    //动态注册,自定义函数
    if (!registerNatives(env))
    
        return -1;
    

    return JNI_VERSION_1_4;

动态注册

RegisterNatives,动态注册就是在这里完成的,函数原型在jni.h中

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)

该函数有3个参数

  • clazz

java类名,通过FindClass得到

  • methods

JNINativeMethod的结构体指针

  • mMethods

方法个数

在该例子中代码如下

//注册Native
static int registerNatives(JNIEnv *env) 
    const char *className = "xsf/jnidemo/NDKUtils"; //指定注册的类
    return registerNativeMethods(env, className, methods, sizeof(methods) / sizeof(methods[0]));


static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMethods) 
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) 
        return JNI_FALSE;
    

    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) 
        return JNI_FALSE;
    

    return JNI_TRUE;

这样动态注册就完成了

0x01 效果图&code

学习code上传到github https://github.com/xsfelvis/JniDemo

0x02 参考

[1] http://blog.csdn.net/yanbober/article/details/51027520
[2] http://tools.android.com/tech-docs/new-build-system/gradle-experimental
[3] http://blog.csdn.net/sbsujjbcy/article/details/48469569
[4] http://www.jianshu.com/p/7844aafe897d
[5] http://blog.majiajie.me/2016/03/27/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%9C%B0%E4%BD%BF%E7%94%A8NDK/

我的c语言学习进阶之旅介绍一下ndk开发中关于jni函数的两种注册方式:静态注册和动态注册(代码片段)

...,而是使用了动态注册方法。下面我们分别来讲一下两种方式的区别。二、静态注册2.1实现原理根据函数名来建立java方法和JNI函数间的一一对应关系。2.2实现过程编写.java代码;编译.java代码,生成.class文件;用... 查看详情

我的c语言学习进阶之旅介绍一下ndk开发中关于jni函数的两种注册方式:静态注册和动态注册(代码片段)

...,而是使用了动态注册方法。下面我们分别来讲一下两种方式的区别。二、静态注册2.1实现原理根据函数名来建立java方法和JNI函数间的一一对应关系。2.2实现过程编写.java代码;编译.java代码,生成.class文件;用... 查看详情

jni知识点总结(代码片段)

...于在Java程序中调用本地代码(例如C/C++代码)。JNI中涉及两种类型的方法注册:静态注册和动态注册。静态注册是指将本地方法的名称和实现直接映射到Java类的静态方法。这可以通过在C/C++代码中使用特定命名模式(例如Java_com_... 查看详情

你应该了解的jni知识——java与jni互相调用(代码片段)

...册与动态注册中,了解了JNI是如何使用的,以及两种注册方式的使用以及区别。本篇博客将介绍Java和JNI的互相调用,因此主要包括两部分:JNI层调用Java层Java层调用JNI、Native层JNI层调用Java层JNI层调用Java层有点类... 查看详情

22jni-动态注册与jni线程(代码片段)

AndroidNdk学习笔记(目录)//1静态注册native函数publicnativevoidstaticRegister();//---------------------------静态注册---------------------------------extern"C"JNIEXPORTvoidJNICALLJava_com_cn_mynat 查看详情

jni学习---注册native函数(代码片段)

注册native函数有两种方法:静态注册和动态注册。静态注册方法根据函数名找到对应的JNI函数,Java层调用函数时,会从对应的JNI中寻找该函数,如果没有就会报错,如果存在则会建立一个关联联系,以后... 查看详情

注册jni函数的两种方式

...外一种方法(动态注册)来克服这些弊端。注册JNI函数的两种方法静态方法这种方法我们比较常见,但比较麻烦,大致流程如下:-先创建Java类,声明Nat 查看详情

androidjni详解(代码片段)

目录一、前言:二、JNI简介三、JNI函数注册3.1静态注册:3.2动态注册四、函数签名 4.1什么是函数签名:4.2为什么需要函数的签名:4.3如何获取函数的签名五、JNIEnv5.1何为JNIEnv:5.2通过JNIEnv调用java对象方法5.3跨线程... 查看详情

androidbinder原理系统服务的注册过程(代码片段)

关联系列AndroidAOSP基础系列Android系统启动系列应用进程启动系列Android深入四大组件系列Android深入理解Context系列Android深入理解JNI系列Android解析WindowManagerAndroid解析WMS系列Android解析AMS系列Android包管理机制系列Android输入系统系列... 查看详情

androidndk-jni注册native方法(代码片段)

静态注册静态注册的格式如下:extern"C"JNIEXPORT[JNI参数类型]JNICALLJava_[包名]_[类名]_[方法名](JNIEnv*env,jobject,[JNI参数类型,参数名])包名也用_分隔。包名、类名、方法名必须和Java中的一模一样。例子:extern"C"... 查看详情

androidndk-jni注册native方法(代码片段)

静态注册静态注册的格式如下:extern"C"JNIEXPORT[JNI参数类型]JNICALLJava_[包名]_[类名]_[方法名](JNIEnv*env,jobject,[JNI参数类型,参数名])包名也用_分隔。包名、类名、方法名必须和Java中的一模一样。例子:extern"C"... 查看详情

jni基本使用二(代码片段)

介绍   接着上一篇博文本次主要介绍JNI的动态注册,以及在JNI中如何开线程。JNI的基础部分,请详细参考JNI的基本使用一动态注册   与静态注册相比,有如下优缺点:   优点:     1.native中函数名简洁,方便记忆... 查看详情

jni基本使用二(代码片段)

介绍   接着上一篇博文本次主要介绍JNI的动态注册,以及在JNI中如何开线程。JNI的基础部分,请详细参考JNI的基本使用一动态注册   与静态注册相比,有如下优缺点:   优点:     1.native中函数名简洁,方便记忆... 查看详情

你应该了解的jni知识——静态注册与动态注册(代码片段)

最近一直在做native这边的跨平台开发,整个结构基本就是下图:大体说来就是,底层C/C++代码。那么对于两端分别有不同的处理:对于Android端而言,由于需要给Java端使用,因此需要提供JNI接口,... 查看详情

ndk之c调用java方法以及动态注册(代码片段)

...关系呢?这就涉及到jni函数的注册,注册方式有两种:静态注册和动态注册。静态注册采用基于约定的命名规则(Java_开头,后接类的全限定名加下划线,方法名这三个组成部分组成 查看详情

ndk之c调用java方法以及动态注册(代码片段)

...关系呢?这就涉及到jni函数的注册,注册方式有两种:静态注册和动态注册。静态注册采用基于约定的命名规则(Java_开头,后接类的全限定名加下划线,方法名这三个组成部分组成,如下代码所示ÿ... 查看详情

androidframework实战开发-binder通信java及jni部分源码分析(代码片段)

...#xff1a;QQ交流群:422901085进行课程讨论android跨进程通信实战视频课程(加群获取优惠)1、第一部分进程与ServiceManager通信及客户端程序发起调用详解平时大部分获取ServiceManager中的某一个服务,一般都是最后都是Serv... 查看详情

androidframework实战开发-binder通信java及jni部分源码分析(代码片段)

...#xff1a;QQ交流群:422901085进行课程讨论android跨进程通信实战视频课程(加群获取优惠)1、第一部分进程与ServiceManager通信及客户端程序发起调用详解平时大部分获取ServiceManager中的某一个服务,一般都是最后都是Serv... 查看详情