JNI函數動態註冊進階

函數動態註冊 這篇文章的結尾提到了一個"動態註冊"的工做效率問題。當咱們在大型的項目中,須要在底層實現一個功能時,咱們會在 Java 層聲明一個 native 方法,那麼在 JNI 層必須有一個本地函數相對應,咱們知道"動態註冊"的一個好處是能夠隨意定義函數的名子,函數的類型也能夠經過 javah 命令得到。可是咱們有沒有思考過一個問題,若是隻是簡單的添加一個JNI函數就須要使用一次javah命令,這樣的工做效率是否是過低了?那怎麼樣才能快速寫出函數的類型呢?這就是本文要講的東西。java

例子剖析

咱們經過一個例子來感覺下今天要學習的內容。android

假設如今有一個 JavaHello.javac++

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_前綴,這樣一眼就能看出來對應關係。學習

返回值: 返回值如何肯定呢?實際上是有一個類型的對應關係,在這個例子中,Javaint類型在JNI中對應jint類型。spa

第一個參數: 這個參數是固定的,它是指向JNIEnv的指針。指針

第二個參數: 表明的是Java對象或者Java類。若是調用Javanative方法的是對象,那麼JNI層對應的就是jobject類型,若是調用Javanative方法是類,也就是說調用的是靜態的native方法,那麼JNI層對應的就是jclass類型。code

JNI層函數剩下的參數就是和Java層的native層的函數一一對應的,在這個例子中,JavaString類型對應的就是JNIjstring類型。

咱們是否是忽然發現,原來咱們須要掌握Java類型和JNI類型的對應關係。

函數動態註冊 這篇文章中可知,咱們還須要知道函數的簽名,我也能夠很快的寫出來

static const JNINativeMethod nativeMethods[] = {
        {"static_test", "(Ljava/lang/String;)I", (void *)native_static_test},
        {"test", "(Ljava/lang/String;)I", (void *)native_test}
};
複製代碼

其中的關鍵就是要掌握函數的類型簽名怎麼寫。

看完這篇文章剩下的內容你就能明白這一切。

JNI類型

基本類型

對於基本類型的對等關係呢,咱們能夠用一張表來講明

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 對應 jstringClass類型對應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

基本類型中,除了 booleanZ 以及 longJ 表示簽名外,其餘的都是用大寫的首字母表示,例如 intI 表示類型簽名。

除了基本類型,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;
}
複製代碼

到此,函數"動態註冊"技術纔算是真的講完了。

相關文章
相關標籤/搜索