在 函数动态注册 这篇文章的结尾提到了一个"动态注册"的工做效率问题。当咱们在大型的项目中,须要在底层实现一个功能时,咱们会在 Java
层声明一个 native
方法,那么在 JNI
层必须有一个本地函数相对应,咱们知道"动态注册"的一个好处是能够随意定义函数的名子,函数的类型也能够经过 javah
命令得到。可是咱们有没有思考过一个问题,若是只是简单的添加一个JNI
函数就须要使用一次javah
命令,这样的工做效率是否是过低了?那怎么样才能快速写出函数的类型呢?这就是本文要讲的东西。java
咱们经过一个例子来感觉下今天要学习的内容。android
假设如今有一个 Java
类 Hello.java
c++
public class Hello {
native int test(String msg);
static native int static_test(String msg);
}
复制代码
Hello.java
有两个 native
函数,最大的区别就是一个静态的,一个是非静态的,那么调用方式固然也不同,你们应该都明白。数组
如今我要使用"动态注册"技术,那么首先就要面临一个问题,JNI
层的函数怎么写?我能够在不使用 javah
命令的前提下,把这个函数手写出来函数
jint native_test(JNIEnv * env, jobject thiz, jstring msg);
jint native_static_test(JNIEnv * env, jclass clazz, jstring msg) 复制代码
可能有人以为我在吹牛逼,那么我如今来解释下这个函数是怎么写出来的。post
函数名: 从 函数动态注册 这篇文章中可知,"动态注册"的JNI
层的函数名能够随意取的,我把Java
层的native
方法前加一个native_
前缀,这样一眼就能看出来对应关系。学习
返回值: 返回值如何肯定呢?实际上是有一个类型的对应关系,在这个例子中,Java
的int
类型在JNI
中对应jint
类型。spa
第一个参数: 这个参数是固定的,它是指向JNIEnv
的指针。指针
第二个参数: 表明的是Java
对象或者Java
类。若是调用Java
的native
方法的是对象,那么JNI
层对应的就是jobject
类型,若是调用Java
的native
方法是类,也就是说调用的是静态的native
方法,那么JNI
层对应的就是jclass
类型。code
JNI
层函数剩下的参数就是和Java
层的native
层的函数一一对应的,在这个例子中,Java
的String
类型对应的就是JNI
的jstring
类型。
咱们是否是忽然发现,原来咱们须要掌握Java
类型和JNI
类型的对应关系。
在函数动态注册 这篇文章中可知,咱们还须要知道函数的签名,我也能够很快的写出来
static const JNINativeMethod nativeMethods[] = {
{"static_test", "(Ljava/lang/String;)I", (void *)native_static_test},
{"test", "(Ljava/lang/String;)I", (void *)native_test}
};
复制代码
其中的关键就是要掌握函数的类型签名怎么写。
看完这篇文章剩下的内容你就能明白这一切。
对于基本类型的对等关系呢,咱们能够用一张表来讲明
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bit |
byte | jbyte | signed 8 bit |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
对于Java
的基本类型,对应的JNI
类型只须要在前面加一个j
前缀便可,很是好记。
除了基本类型外,其余全部 Java
对象在 JNI
对应的类型为 jobject
。然而为了方便还定义了一些其余 Java
类型对应的 JNI
类型,例如 String
对应 jstring
,Class
类型对应jclass
。我用官网的一张图来描述对应关系。
若是你使用的是C
语言,那么这些引用类型之间的关系以下
/* * Reference types, in C. */
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
复制代码
能够看到 jstring
, jclass
的类型其实都是 jobject
的别名,而 jobject
的类型竟然是一个通知指针类型 void *
,咱们是否是彷佛明白了点什么呢,毕竟C语言都是通知指针来控制数据的,用一个通用指针类型void *
表示全部 Java
类型应该不过度吧~
而若是你用的是 C++
语言,那么这些引用类型关系又是如何呢
/* * Reference types, in C++ */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
复制代码
原来 _jclass
和 _jstring
都是空继承于 _jobject
,而 _jobject
是一个没有任何声明的类。这个就有点费解了,虚拟机是如何完成 Java
引用类型到 _jobject
的转换的?我还木鸡~
JNI
使用了虚拟机的类型签名的表示方法,咱们先看下基本类型签名
Type Signature | Java Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
基本类型中,除了 boolean
用 Z
以及 long
用 J
表示签名外,其余的都是用大写的首字母表示,例如 int
用 I
表示类型签名。
除了基本类型,Java
其余类型的签名如何表示呢,基本格式以下
Type Signature | Java Type |
---|---|
L fully-qualified-class ; | fully-qualified-class |
什么意思呢?例如 String
类型的全路径为 java.lang.String
,那么对应的签名为 Ljava/lang/String;
。注意了,对于引用类型的签名,签名首字符必定为L
,签名末尾字符必定为;
。
数组也有必定的签名要求,以下表
Type Signature | Java Type |
---|---|
[type | type[] |
怎么理解呢?举两个例子就知道了。
因为int
类型的签名为 I
,所以 int[]
签名就为 [I
。
因为java.lang.String
类型的签名为Ljava/lang/String;
,那么 String[]
的签名为 [Ljava/lang/String;
。
你们对着例子好好理解,容易看迷糊。
因为Java
有方法重载,然而JNI
可没有对应的函数重载功能,所以每一个Java
方法都有惟一的方法签名才能区分出方法重载
Type Signature | Java Type |
---|---|
( arg-types ) ret-type | method type |
这个也很差理解,咱们举个例子。假如如今有个Java
方法
String f(int a);
复制代码
f()
方法的返回类型为String
,对应签名为Ljava/lang/String;
,参数为int
类型,对应的签名为I
,那么整个方法的签名为(I)Ljava/lang/String;
。
看完了上面讲的类型以及签名,再结合函数动态注册 这篇文章所讲的"动态注册"方法,你是否能手写出动态注册的代码呢?
做为这篇文章的结尾,我就使用文章开头用到的例子,手写出动态注册的代码
#include "hello.h"
#include <android/log.h>
#define LOG_TAG "david"
jint native_test(JNIEnv * env, jobject object,jstring msg) {
const char * p_msg = env->GetStringUTFChars(msg, false);
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "method = %s, msg = %s", __FUNCTION__, p_msg);
return 0;
}
jint native_static_test(JNIEnv * env, jclass clazz, jstring msg) {
const char * p_msg = env->GetStringUTFChars(msg, false);
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "method = %s, msg = %s", __FUNCTION__, p_msg);
return 0;
}
static const JNINativeMethod nativeMethods[] = {
{"static_test", "(Ljava/lang/String;)I", (void *)native_static_test},
{"test", "(Ljava/lang/String;)I", (void *)native_test}
};
static int registerNativeMethods(JNIEnv *env) {
int result = -1;
jclass class_hello = env->FindClass("com/umx/ndkdemo/Hello");
if (env->RegisterNatives(class_hello, nativeMethods,
sizeof(nativeMethods) / sizeof(nativeMethods[0])) == JNI_OK) {
result = 0;
}
return result;
}
jint JNI_OnLoad(JavaVM * vm, void * reserved) {
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_1) == JNI_OK) {
if (registerNativeMethods(env) == JNI_OK) {
result = JNI_VERSION_1_6;
}
}
return result;
}
复制代码
到此,函数"动态注册"技术才算是真的讲完了。