望远山,知近路,而后自得其乐!

Android JNI开发详解(2)-函数注册

1. JNI开发流程

  1. 创建Native C++工程,这部分可用参考Android JNI开发详解(2)-开发环境搭建-开发环境搭建.md)
  2. 创建Java层本地接口调用类,并定义好相应的本地函数。
  3. 将Java源代码编译成class字节码文件(Android studio会自动生成)。
  4. 创建对应的本地函数接口,并注册本地函数,Jni函数注册有两种方式,静态注册和动态注册,静态注册使用javah工具自动生成对应的本地接口函数定义,动态注册则需要在JNI_OnLoad函数中调用RegisterNatives来完成注册。
  5. 实现本地函数接口函数的具体功能实现。
  6. 修改CMakeLists.txtAndroid.mk
  7. 编译测试。

android_package_process.png

2. JNI函数注册

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

JNI技术是Java世界与Native世界的通信桥梁,具体到代码,Java层的代码如何同Native层的代码进行调用的呢?我们都知道,在调用native方法之前,首先要调用System.loadLibrary接口加载一个实现了native方法的动态库才能正常访问,否则就会抛出java.lang.UnsatisfiedLinkError异常 。 那么,在Java中调用某个native方法时,JVM是通过什么方式,能正确的找到动态库中C/C++实现的那个native函数呢?

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

  1. 按照JNI规范的命名规则。
  2. 调用JNI提供的RegisterNatives函数,将本地函数注册到JVM中。

第一种方式,可用使用javah工具按照Java类中定义的native方法,按照JNI规范的命名规则的方式自动生成Jni本地C/C++头文件。第二种方式则需要在本地库的JNI_OnLoad函数中调用RegisterNatives来动态注册。

JNI函数注册是将Java层声明的Native方法同实际的Native函数绑定起来的实现方式,也就是说,只要通过JNI函数注册机制注册了本地方,Java层就可以直接调用定义的这些本地方法了。对应上述JVM查找native方法的两种方式,JNI函数注册方式一般分为静态注册和动态注册两种方式。

2.2 javah工具和javap工具

为了方便我们进行完成注册操作,我们经常还会用到javah和javap两个命令行工具。

  • javah工具用于生产本地方法头文件

在JDK1.7中,在src目录(android studio工程在src/main/java目录)下执行javah 全类名

在JDK1.6中,在bin/classes目录下执行

执行前需要先编译通过(编译为class文件),大多数IDE会自动执行编译,因此不需要在手动进行编译

  • javap工具用于打印方法签名

2.3 静态注册

静态注册实际十分简单,就是使用上面提到的javah工具为我们生产本地方法头文件,然后在根据生成的头文件完成相应的函数即可。静态注册即本地函数按照特定的命名规则命名本地方法,使得这些本地方法与Java类中定义的本地方法一一对应,在执行JNI调用时,只需要根据命名规则去调用so库中对应的方法即可。

下面时一个使用静态注册的例子,Test类定义了Java中的本地方法。

package cc.ccbu.jnitest;

public class Test {
    static {
        System.loadLibrary("native-lib");
    }

    public native String textFromJni();
}

使用javah生成对应的本地方法头文件。

#include <jni.h>
/* Header for class cc_ccbu_jnitest_Test */

#ifndef _Included_cc_ccbu_jnitest_Test
#define _Included_cc_ccbu_jnitest_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     cc_ccbu_jnitest_Test
 * Method:    textFromJni
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_cc_ccbu_jnitest_Test_textFromJni
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif

我们只用在我们的.c或cpp文件实现Java_cc_ccbu_jnitest_Test_textFromJni方法即可。

2.4 动态注册

对应与上面的静态注册方法,还有一种动态注册JNI函数的方式,即动态注册。动态注册是当Java层调用System.loadLibrary方法加载so库后,本地库的JNI_OnLoad函数会被调用,在JNI_OnLoad函数中通过调用RegisterNatives函数来完成本地方法的注册。

本地so库加载和卸载时,会调用JNI_OnLoad和JNI_OnUnload函数,函数原型如下:

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

动态注册本地方法的函数RegisterNatives原型如下:

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

其中JNINativeMethod结构体用来描述本地方法结构,其定义如下:

typedef struct {
    const char* name;      // Java方法名
    const char* signature; // Java方法签名
    void* fnPtr;           // jni本地方法对应的函数指针
} JNINativeMethod;

下面通过一个例子来展示jni动态注册本地方法的过程。还是以上面静态注册的Test类为例

  1. 在Java文件中定义本地方法,加载本地so库
package cc.ccbu.jnitest;
   
public class Test {
   static {
       System.loadLibrary("native-lib");
   }

   public native String textFromJni();
}
  1. 在JNI_OnLoad函数中注册本地方法
jstring textFromJni(JNIEnv* env, jobject thiz) {
    return env->NewStringUTF("text from jni");
}
   
static JNINativeMethod gMethods[] = {
   {"textFromJni", "()Ljava/lang/String;", (void*)textFromJni}
};
   
int registerMethod(JNIEnv *env) {
   jclass test = env->FindClass("cc/ccbu/jnitest/Test");
   return env->RegisterNatives(test, gMethods, sizeof(gMethods)/ sizeof(gMethods[0]));
}
   
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
       JNIEnv* env = NULL;
   if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
       return JNI_ERR;
   }
   
   if (registerMethod(env) != JNI_OK) {
        return JNI_ERR;
   }
   
   return  JNI_VERSION_1_6;
}

文章评论已关闭!