关闭

注册JNI函数的两种方式

4889人阅读 评论(1) 收藏 举报
分类:

前言

前面介绍过如何实现在Android Studio中制作我们自己的so库,相信大家看过之后基本清楚如何在Android studio创建JNI函数并最终编译成不同cpu架构的so库,但那篇文章介绍注册JNI函数的方法(静态方法)存在一些弊端,本篇将介绍另外一种方法(动态注册)来克服这些弊端。

注册JNI函数的两种方法

静态方法

这种方法我们比较常见,但比较麻烦,大致流程如下:
- 先创建Java类,声明Native方法,编译成.class文件。
- 使用Javah命令生成C/C++的头文件,例如:javah -jni com.devilwwj.jnidemo.TestJNI,则会生成一个以.h为后缀的文件com_devilwwj_jnidemo_TestJNI.h
- 创建.h对应的源文件,然后实现对应的native方法,如下图所示:

这里写图片描述

说一下这种方法的弊端:

  • 需要编译所有声明了native函数的Java类,每个所生成的class文件都得用javah命令生成一个头文件。
  • javah生成的JNI层函数名特别长,书写起来很不方便
  • 初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率
    摘自:深入理解Android卷I

既然有这么多弊端,我们自然要考虑一下有没有其他更好的方法下一节就是我要讲的替代方法,Android用的也是这种方法。

动态注册

我们知道Java Native函数和JNI函数时一一对应的,JNI中就有一个叫JNINativeMethod的结构体来保存这个对应关系,实现动态注册方就需要用到这个结构体。举个例子,你就一下子明白了:

声明native方法还是一样的:

public class JavaHello {
    public static native String hello();
}

创建jni目录,然后在该目录创建hello.c文件,如下:

//
// Created by DevilWwj on 16/8/28.
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>

/**
 * 定义native方法
 */
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)
{
    printf("hello in c native code./n");
    return (*env)->NewStringUTF(env, "hello world returned.");
}

// 指定要注册的类
#define JNIREG_CLASS "com/devilwwj/library/JavaHello"

// 定义一个JNINativeMethod数组,其中的成员就是Java代码中对应的native方法
static JNINativeMethod gMethods[] = {
    { "hello", "()Ljava/lang/String;", (void*)native_hello},
};


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;
}

/***
 * 注册native方法
 */
static int registerNatives(JNIEnv* env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

/**
 * 如果要实现动态注册,这个方法一定要实现
 * 动态注册工作在这里进行
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    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;
    }
    result = JNI_VERSION_1_4;

    return result;

}

先仔细看一下上面的代码,看起来好像多了一些代码,稍微解释下,如果要实现动态注册就必须实现JNI_OnLoad方法,这个是JNI的一个入口函数,我们在Java层通过System.loadLibrary加载完动态库后,紧接着就会去查找一个叫JNI_OnLoad的方法。如果有,就会调用它,而动态注册的工作就是在这里完成的。在这里我们会去拿到JNI中一个很重要的结构体JNIEnv,env指向的就是这个结构体,通过env指针可以找到指定类名的类,并且调用JNIEnv的RegisterNatives方法来完成注册native方法和JNI函数的对应关系。

我们在上面看到声明了一个JNINativeMethod数组,这个数组就是用来定义我们在Java代码中声明的native方法,我们可以在jni.h文件中查看这个结构体的声明:

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

结构体成员变量分别对应的是Java中的native方法的名字,如本文的hello;Java函数的签名信息、JNI层对应函数的函数指针。

以上就是动态注册JNI函数的方法,上面只是一个简单的例子,如果你还想再实现一个native方法,只需要在JNINativeMethod数组中添加一个元素,然后实现对应的JNI层函数即可,下次我们加载动态库时就会动态的将你声明的方法注册到JNI环境中,而不需要你做其他任何操作。

总结

关于JNI技术,在Android中使用是非常多的,我们在实际开发中或多或少可能会使用到第三方或者需要自己开发相应的so库,所以学习和理解JNI中的一些实现原理还是很有必要的,从以前在Eclipse来实现so库开发到现在可以通过Android Studio来开发so库,会发现会方便很多,这个也是技术的发展带来的一些便捷。笔者也计划学习NDK开发的相关技术,后续也会将自己学到的内容总结分享出来。

4
0
查看评论

Android 动态注册JNI函数

1.JNI函数注册方式 在Android开发中,由于种种原因我们需要调用C/C++代码,在这个时候我们就需要使用jni了, jni在使用时要对定义的函数进行注册,这样java才能通过native关键字定义的方法找到对应的C/C++函数 注册函数的方法有两种: 静态注册和动态注册 2.静态...
  • u010925331
  • u010925331
  • 2016-03-27 16:50
  • 1710

初识JNI(二)-静态注册和动态注册

为什么需要注册? ,这个其实就是给Java的native函数找到底层C,C++实现的函数指针。 静态注册,通过包名类名一致来确认,Java有一个命令javah,专门生成某一个JAVA文件所有的native函数的头文件(h文件),步骤如下,我们只说android项目下如何实施,其实理解了都一样 1.编...
  • u014623470
  • u014623470
  • 2015-11-05 16:10
  • 1244

JNI/NDK开发指南(八)——调用构造方法和父类实例方法

转载请注明出处:http://blog.csdn.net/xyang81/article/details/44002089在第6章我们学习到了在Native层如何调用Java静态方法和实例方法,其中调用实例方法的示例代码中也提到了调用构造函数来实始化一个对象,但没有详细介绍,一带而过了。还没有阅读过...
  • xyang81
  • xyang81
  • 2015-03-01 21:18
  • 6210

JNI学习积累之一 ---- 常用函数大全

本文原创,转载请注明出处:http://blog.csdn.net/qinjuning                最近一段时间,在工作方面比较闲,分配的Bug不是很多,于是好好利用这段时间...
  • qinjuning
  • qinjuning
  • 2012-05-23 17:40
  • 45195

JNI语法 JNI参考 JNI函数大全

原文地址:JNI语法 JNI参考 JNI函数大全 内容太多,请按Ctrl+F查找你需要的信息。   一、对照表 Java类型    本地类型         描述...
  • Buaaroid
  • Buaaroid
  • 2016-05-10 16:47
  • 1126

Android JNI调用函数命名原则规范

package com.test.mytest; public final class SensorNative { static { System.loadLibrary("MYJNI"); Init(); } private native static vo...
  • u011467537
  • u011467537
  • 2015-11-20 07:40
  • 2389

JNI:通过函数名对应表的方式来加载对应的native方法

接上一篇  Eclipse + vs2013 编写第一个 JNI HelloWorld>>  ,读者一定很奇怪我们在HelloWorld.java中定义的private native void print()调用时, 怎么就会调用到我们底层的Java_com_wort...
  • qq_36235279
  • qq_36235279
  • 2017-01-13 14:36
  • 939

JNI学习积累之三 ---- 操作JNI函数以及复杂对象传递

本文原创,转载请注明出处:http://blog.csdn.net/qinjuning          在掌握了JNI函数的使用和相关类型的映射后,以及知晓何利用javah工具生成对应的jni函数以及如何生成动态 ...
  • qinjuning
  • qinjuning
  • 2012-05-27 21:19
  • 49581

自动生成Jni中Java调用C语言实现的签名函数名

首先编写一个类例如HelloWorld.java类文件 把你要在Java代码中声明的调用C语言的native函数声明写在Helloworld.java代码中 例如: public class HelloWorld{ public native String HelloFromC() ; ...
  • zhangshan415
  • zhangshan415
  • 2013-12-11 15:51
  • 1780

Dalvik虚拟机JNI方法的注册过程分析

在前面一文中,我们分析了Dalvik虚拟机的运行过程。从中可以知道,Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个JNI方法,那么就会直接跳到它的地址去执行。也就是说,JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行。由此也可看出,JNI方法是And...
  • Luoshengyang
  • Luoshengyang
  • 2013-05-27 00:57
  • 52194
    个人资料
    • 访问:5045963次
    • 积分:44459
    • 等级:
    • 排名:第89名
    • 原创:660篇
    • 转载:80篇
    • 译文:3篇
    • 评论:2760条
    博客专栏
    尽情骚扰我

    微信公众号:wwjblog

    微信号:whatswwj


    移动开发者狂热群:注明入群理由,里面有一群热爱分享的开发者
    • Q群:299402133
    • QQ号:659982592

    高品质课堂推荐:http://stay4it.com


    最新评论
    【程序员的成长路线图】
    从入门到成长到成熟再到优秀,大多数程序员走了前面一段相似的道路,而有些人却走得更远一些!!!!