1. JNI开发流程
- 创建Native C++工程,这部分可用参考Android JNI开发详解(2)-开发环境搭建-开发环境搭建.md)
- 创建Java层本地接口调用类,并定义好相应的本地函数。
- 将Java源代码编译成class字节码文件(Android studio会自动生成)。
- 创建对应的本地函数接口,并注册本地函数,Jni函数注册有两种方式,静态注册和动态注册,静态注册使用
javah
工具自动生成对应的本地接口函数定义,动态注册则需要在JNI_OnLoad
函数中调用RegisterNatives
来完成注册。 - 实现本地函数接口函数的具体功能实现。
- 修改
CMakeLists.txt
或Android.mk
。 - 编译测试。
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方法有两种方式:
- 按照JNI规范的命名规则。
- 调用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类为例
- 在Java文件中定义本地方法,加载本地so库
package cc.ccbu.jnitest;
public class Test {
static {
System.loadLibrary("native-lib");
}
public native String textFromJni();
}
- 在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;
}