Android深入理解JNI(一)JNI 靜態註冊與動態註冊

 

1.JNI概述

Android系統按語言來劃分的話由兩個世界組成,分別是Java世界和Native世界。那爲什麼要這麼劃分呢?Android系統由Java寫不好嗎?除了性能的之外,最主要的原因就是在Java誕生之前,就有很多程序和庫都是由Native語言寫的,因此,重複利用這些Native語言編寫的庫是十分必要的,況且Native語言編寫的庫具有更好的性能。
這樣就產生了一個問題,Java世界的代碼要怎麼使用Native世界的代碼呢,這就需要一個橋樑來將它們連接在一起,而JNI就是這個橋樑。
未命名文件(5).png
通過JNI,Java世界的代碼就可以訪問Native世界的代碼,同樣的,Native世界的代碼也可以訪問Java世界的代碼。
 

靜態註冊

原理:根據函數名來建立 java 方法與 JNI 函數的一一對應關係;

實現流程:

    編寫 java 代碼;
    利用 javah 指令生成對應的 .h 文件;
    對 .h 中的聲明進行實現;

弊端:

    編寫不方便,JNI 方法名字必須遵循規則且名字很長;
    編寫過程步驟多,不方便;
    程序運行效率低,因爲初次調用native函數時需要根據根據函數名在JNI層中搜索對應的本地函數,然後建立對應關係,這個過程比較耗時;

extern "C" JNIEXPORT jstring JNICALL
Java_cn_com_jni_dexshell_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject jobj/* this */,jstring id) {
 
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello .c_str());
}

動態註冊

原理:利用 RegisterNatives 方法來註冊 java 方法與 JNI 函數的一一對應關係;

實現流程:

    利用結構體 JNINativeMethod 數組記錄 java 方法與 JNI 函數的對應關係;
    實現 JNI_OnLoad 方法,在加載動態庫後,執行動態註冊;
    調用 FindClass 方法,獲取 java 對象;
    調用 RegisterNatives 方法,傳入 java 對象,以及 JNINativeMethod 數組,以及註冊數目完成註冊;

優點:

    流程更加清晰可控;
    效率更高;

 

JNINativeMethod

在動態註冊的過程中使用到了結構體 JNINativeMethod 用於記錄 java 方法與 jni 函數的對應關係

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

結構體的第一個參數 name 是java 方法名;

第二個參數 signature 用於描述方法的參數與返回值;

第三個參數 fnPtr 是函數指針,指向 jni 函數;

其中,第二個參數 signature 使用字符串記錄方法的參數與返回值,具體格式形如“()V”、“(II)V”,其中分爲兩部分,括號內表示的是參數,括號右側表示的是返回值;
 

數據類型映射

    基本數據類型

java 類型  native 類型  域描述符 
boolean  jboolean 
byte  jbyte 
char  jchar 
short  jshort 
int  jint 
long  jlong 
float  jfloat 
double  jdouble 
void  void 

2. 數組引用類型

如果是一維數組則遵循下表,如果是二維數組或更高維數組則對應的 native 類型爲 jobjectArray,域描述符中使用 ‘[’ 的個數表示維數

java 類型  native 類型  域描述符 
int[]  jintArray  [I 
float[]  jfloatArray  [f 
byte[]  jbyteArray  [B 
char[]  jcharArray  [C 
short[]  jshortArray  [S 
double[]  jdoubleArray  [D 
long[]  jlongArray  [F 
boolean[]  jbooleanArray  [Z

3. 對象引用類型

對於其它引用類型,即 java 中的對象,其映射規則爲

java 類型  native 類型  域描述符 
類名(如 Surface)  通常是 jobject,僅有一種例外,如果 java 類型是 String,則對應的native 類型是 jstring  以”L”開頭,以”;”結尾中間是用”/” 隔開的包及類名(如 Landroid/view/Surface;)如果內部類則使用$連接內部類;

4. 對象數組引用類型

如果是一維數組則遵循下表,如果是二維數組或更高維數組則對應的 native 類型爲 jobjectArray,域描述符中使用 ‘[’ 的個數表示維數

java 類型  native 類型  域描述符 
類名(如 Surface)  通常是 jobject,僅有一種例外,如果 java 類型是 String,則對應的native 類型是 jstring  在對象引用類型的域描述符的基礎上在左邊添加’[‘字符

 

jni 函數默認參數

在 jni 函數中有兩個默認參數

JNIEnv *env, jobject thiz

 其中 JNIEnv 指代的是當前 java 環境,可以利用 JNIEnv 可以操作 java 層代碼;jobject 指代的是 jni 函數對應的 java native 方法的類實例,如果 java 方法是 static,則代表的是 class 對象;

例:

java

package cn.com.jni.jniautoreg;

public class TestJNI {
    public static native void  native_init();
    public static native String    stringFromJNI(String name,String address);
}

 native

#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <memory.h>

#define LOG_TAG "my-jni"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
using namespace std;
static const char *const kClassJniTest =
        "cn/com/jni/jniautoreg/TestJNI";

 
jstring JNI_stringFromJNI(JNIEnv *env, jobject obj, jstring name,jstring address) {
    LOGE("JAVA  call  JNI stringFromJNI");
    string hello = "Hello from JNI";
    return env->NewStringUTF(hello.c_str());
}



void JNI_native_init(JNIEnv *env, jobject thizz) {
    LOGE("  native_1init");

}

static const JNINativeMethod gMethods[] = {
        {
                "native_init",
                "()V",
                (void *) JNI_native_init
        },

       {
                "stringFromJNI",
                "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
                (void *) JNI_stringFromJNI
        },
};

static int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *gMethod,
                                 int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        LOGI(" JNI reg faild:%s",className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
        LOGI(" JNI reg method failed:%s",gMethod->name);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static int register_along_jni(JNIEnv *env) {

    return registerNativeMethods(env, kClassJniTest, gMethods,
                                 NELEM(gMethods));
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /* reserved */) {

    JNIEnv *env = NULL;
    jint result = -1;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("ERROR: JNI version error");

        return JNI_ERR;
    }
    if (register_along_jni(env) == -1) {
        LOGE("ERROR:  JNI_OnLoad failed");
        return JNI_ERR;
    }
    result = JNI_VERSION_1_6;
    return result;
}

 
注意:
在JNI_OnLoad函數的結尾處,我們一定要有返回值,而且必須是JNI_VERSION_1_4 或 JNI_VERSION_1_6,也就是JNI的版本號,我們一定要返回正確的版本號,否則系統也是無法加載的。
 

完整 demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章