jni注册方法
动态注册
代码
java
public class TextJni {
static {
System.loadLibrary("textjni_lib");
}
native int text(String message);
static native int static_text(String message);
}
c
#include <jni.h>
#include <string>
#include <android/log.h>
jint native_text(JNIEnv *env, jobject jobject1, jstring msg) {
const char *p_msg = env->GetStringUTFChars(msg, JNI_FALSE);
__android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__, p_msg);
return 0;
}
jint native_staic_text(JNIEnv *env, jobject jclass1, jstring meg) {
const char *p_msg = env->GetStringUTFChars(meg, JNI_FALSE);
__android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__, p_msg);
return 0;
}
static const JNINativeMethod nativeMethod[] = {
{"text", "(Ljava/lang/String;)I", (void *) native_text},
{"static_text", "(Ljava/lang/String;)I", (void *) native_staic_text}
};
static int registNativeMethod(JNIEnv *env) {
int result = -1;
jclass class_text = env->FindClass("com.text.ndk1.TextJni");
if (env->RegisterNatives(class_text, nativeMethod,
sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
result = 0;
}
return result;
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
int result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
if (registNativeMethod(env) == JNI_OK) {
result = JNI_VERSION_1_6;
}
return result;
}
}
总结
首先调用JNI_OnLoad,JNI_OnLoad里面调用registNativeMethod;
registNativeMethod中,调用RegisterNatives对java中声明的native与c中的方法进行绑定动态注册:
env->RegisterNatives(class_text, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0])
其中nativeMethod数组为:
{"text", "(Ljava/lang/String;)I", (void *) native_text}, {"static_text", "(Ljava/lang/String;)I", (void *) native_staic_text}
原理
在system.load时会去调用JNI_OnLoad,有就注册,没有就不注册。
动态注册的原理:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数, 而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)流程更加清晰可控,效率更高。
Java层通过System.loadLibrary()
方法可以加载一个动态库,此时虚拟机会调用jni库中的JNI_OnLoad()
函数。
jint JNI_OnLoad(JavaVM* vm, void* reserved);
返回值代表,动态库需要的jni版本,如果虚拟机不能识别这个版本,那么就不可以加载这个动态库
目前的返回值有,JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6。
如果动态库没有提供 JNI_OnLoad()
函数会默认使用JNI_VERSION_1_1
版本,但是这个版本太老,很多新函数没有,最好返回版本。
实现流程:
- 利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系。
- 实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册。
- 调用 FindClass 方法,获取 java 对象。
- 调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册。
注册函数:JNI_Onload()
JNI_OnLoad()
函数经常用来做一些初始化操作,动态注册就是在这里进行的
动态注册通过_JNIEnv
的RegisterNatives()
函数来完成
函数原型为
jint RegisterNatives(JNIEnv *env, jclass clazz,
const JNINativeMethod *methods, jint nMethods);
- 第一个参数:JNIEnv *指针
- 第二个参数:代表一个java类
- 第三个参数:代表
JNINativeMethod
结构体数组,JNINativeMethod
定义了java层函数和native层函数的映射关系 - 第四个参数:代表第三个参数methods数组的大小
返回值 0代表成功,负值代表失败
JNINativeMethod结构体
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
- name:代表java的native方法的名字
- signature:表示方法的签名
- fnPtr:是一个函数指针,指向jni层的一个函数,也就是java层和native层建立联系的函数
signature签名
签名符号 | C/C++ | java |
---|---|---|
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
[Z | jbooleanArray | boolean[] |
[I | jintArray | int[] |
[J | jlongArray | long[] |
[D | jdoubleArray | double[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
特殊的String |
||
Ljava/lang/String; | jstring | String |
L完整包名加类名; | jobject | class |
举个例子:
传入的java参数有两个 分别是 int 和 long[] 函数返回值为 String
即函数的定义为:String getString(int a ,long[] b)
签名就应该是 :"(I[J)Ljava/lang/String;"(不要漏掉英文分号)
静态注册
AS很牛,写native很方便,不详说了
package com.example.wenzhe.myjni;
/**
* Created by wenzhe on 16-1-27.
*/
public class JniTest {
public native int getRandomNum();
public native String stringFromJNI();
static {
System.loadLibrary("HelloJni");
}
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_text_ndk1_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
区别总结
静态注册
- 优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
- 缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高
动态注册
- 优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高
- 缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败