引用类型描述符
一般引用类型描述符的规则如下,注意不要丢掉“;”
L + 类描述符 + ;
如,String 类型的域描述符为:
Ljava/lang/String;
数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号
[ + 其类型的域描述符
例如:
int[] 描述符为 [I double[] 描述符为 [D String[] 描述符为 [Ljava/lang/String; Object[] 描述符为 [Ljava/lang/Object; int[][] 描述符为 [[I double[][] 描述符为 [[D
对应在 jni.h 获取 Java 的字段的 native 函数如下,name为 Java 的字段名字,sig 为域描述符
//C jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); //C++ jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) { return functions->GetFieldID(this, clazz, name, sig); } jobject GetObjectField(jobject obj, jfieldID fieldID) { return functions->GetObjectField(this, obj, fieldID); }
3.2 类描述符
类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 / 分割
如,Java 中 java.lang.String 类的描述符为 java/lang/String
native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h 的函数定义如下:
//C jclass (*FindClass)(JNIEnv*, const char*); //C++ jclass FindClass(const char* name) { return functions->FindClass(this, name); }
字符串参数就是类的引用类型描述符,如 Java 对象 cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniTest; 如下:
jclass jclazz = env->FindClass("Lcn/cfanr/jni/JniTest;");
3.3 方法描述符
方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:
(参数……)返回类型
例如:
Java 层方法 ——> JNI 函数签名 String getString() ——> Ljava/lang/String; int sum(int a, int b) ——> (II)Ivoid main(String[] args) ——> ([Ljava/lang/String;)V
另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass 是获取到的类对象,name 是 Java 对应的方法名字,sig 就是上面说的方法描述符
//C jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); //C++ jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) { return functions->GetMethodID(this, clazz, name, sig); }
不需要记住,可以使用下面方法查看签名
java -s -p xxx.class
4 静态注册和动态注册
当执行一个 Java 的 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。
注册分为 静态注册 和 动态注册两种。
4.1 静态注册
定义
通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。
规则
java_完整包名_类名_方法名
特殊规则:
- 包名或类名或方法名中含下划线 _ 要用 _1 连接;
- 重载的本地方法命名要用双下划线 __ 连接;
- 参数签名的斜杠 “/” 改为下划线 “_” 连接,分号 “;” 改为 “_2” 连接,左方括号 “[” 改为 “_3” 连接;
另外,对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。
示例
包名:com.android.javac,类名:NativeAccessJava // java method public native void setJavaString(); // jni method JNIEXPORT void JNICALL Java_com_android_javac_NativeAccessJava_setJavaString(JNIEnv *env, jobject thiz)
4.2 动态注册
动态注册原理
动态注册 JNI 的原理:直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系,步骤:
1> 先编写 Java 的 native 方法;
2> 编写 JNI 函数的实现(函数名可以随便命名);
3> 利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;
4> 利用registerNatives(JNIEnv* env)注册类的所有本地方法;
5> 在 JNI_OnLoad 方法中调用注册方法;
6> 在Java中通过System.loadLibrary加载完JNI动态库之后,会自动调用JNI_OnLoad函数,完成动态注册;
不再使用 JNIEXPORT 和 JNICALL 两个宏定义声明指定的方法,也不用依照固定的命名规则命名方法(不过通常 jni 里的方法名还是保持和 native 方法的方法名一致,见名思义),而是通过了一个 RegisterNatives 方法完成了动态注册。
函数映射表
JNINativeMethod这其实是一个结构体,在jni.h头文件中定义,通过这个结构体从而使Java与jni建立联系
typedef struct { const char* name; //Java中函数的名字 const char* signature;//符号签名,描述了函数的参数和返回值 void* fnPtr;//函数指针,指向一个被调用的函数 } JNINativeMethod;
示例
// java method public native String stringFromJNI(); public static native int add(int a, int b); // jni register jint DyReg::RegisterNatives(JNIEnv *env) { LOGE("RegisterNatives"); jclass clazz = env->FindClass("com/android/javac/DynamicReg"); if (clazz == NULL) { LOGE("could't find class: com/android/javac/DynamicReg"); return JNI_ERR; } JNINativeMethod methods_MainActivity[] = { {"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI}, {"add", "(II)I", (void *) add} }; // int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]); return env->RegisterNatives(clazz, methods_MainActivity, sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0])); }