在 函數動態註冊 這篇文章的結尾提到了一個"動態註冊"的工做效率問題。當咱們在大型的項目中,須要在底層實現一個功能時,咱們會在 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;
}
複製代碼
到此,函數"動態註冊"技術纔算是真的講完了。