Android JNI開發詳解(2)-函數註冊

原文出處:http://www.ccbu.cc/index.php/android/android-jni-function-register.html

1. JNI開發流程

  1. 創建Native C++工程,這部分可用參考[Android JNI開發詳解(2)-開發環境搭建](Android JNI開發工具篇(1)-開發環境搭建.md)

  2. 創建Java層本地接口調用類,並定義好相應的本地函數。

  3. 將Java源代碼編譯成class字節碼文件(Android studio會自動生成)。

  4. 創建對應的本地函數接口,並註冊本地函數,Jni函數註冊有兩種方式,靜態註冊和動態註冊,靜態註冊使用javah工具自動生成對應的本地接口函數定義,動態註冊則需要在JNI_OnLoad函數中調用RegisterNatives來完成註冊。

  5. 實現本地函數接口函數的具體功能實現。

  6. 修改CMakeLists.txtAndroid.mk

  7. 編譯測試。

在這裏插入圖片描述

2. JNI函數註冊

2.1 JVM查找native方法有兩種方式

JNI技術是Java世界與Native世界的通信橋樑,具體到代碼,Java層的代碼如何同Native層的代碼進行調用的呢?我們都知道,在調用native方法之前,首先要調用System.loadLibrary接口加載一個實現了native方法的動態庫才能正常訪問,否則就會拋出java.lang.UnsatisfiedLinkError異常 。 那麼,在Java中調用某個native方法時,JVM是通過什麼方式,能正確的找到動態庫中C/C++實現的那個native函數呢?

JVM查找native方法有兩種方式:

  1. 按照JNI規範的命名規則。

  2. 調用JNI提供的RegisterNatives函數,將本地函數註冊到JVM中。

第一種方式,可用使用javah工具按照Java類中定義的native方法,按照JNI規範的命名規則的方式自動生成Jni本地C/C++頭文件。第二種方式則需要在本地庫的JNI_OnLoad函數中調用RegisterNatives來動態註冊。

JNI函數註冊是將Java層聲明的Native方法同實際的Native函數綁定起來的實現方式,也就是說,只要通過JNI函數註冊機制註冊了本地方,Java層就可以直接調用定義的這些本地方法了。對應上述JVM查找native方法的兩種方式,JNI函數註冊方式一般分爲靜態註冊和動態註冊兩種方式。

2.2 javah工具和javap工具

爲了方便我們進行完成註冊操作,我們經常還會用到javah和javap兩個命令行工具。

  • javah工具用於生產本地方法頭文件

    在JDK1.7中,在src目錄(android studio工程在src/main/java目錄)下執行javah 全類名

    在JDK1.6中,在bin/classes目錄下執行

    執行前需要先編譯通過(編譯爲class文件),大多數IDE會自動執行編譯,因此不需要在手動進行編譯

  • javap工具用於打印方法簽名

2.3 靜態註冊

靜態註冊實際十分簡單,就是使用上面提到的javah工具爲我們生產本地方法頭文件,然後在根據生成的頭文件完成相應的函數即可。靜態註冊即本地函數按照特定的命名規則命名本地方法,使得這些本地方法與Java類中定義的本地方法一一對應,在執行JNI調用時,只需要根據命名規則去調用so庫中對應的方法即可。

下面時一個使用靜態註冊的例子,Test類定義了Java中的本地方法。

package cc.ccbu.jnitest;

public class Test {
    static {
        System.loadLibrary("native-lib");
    }

    public native String textFromJni();
}

使用javah生成對應的本地方法頭文件。

#include <jni.h>
/* Header for class cc_ccbu_jnitest_Test */

#ifndef _Included_cc_ccbu_jnitest_Test
#define _Included_cc_ccbu_jnitest_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     cc_ccbu_jnitest_Test
 * Method:    textFromJni
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_cc_ccbu_jnitest_Test_textFromJni
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif

我們只用在我們的.c或cpp文件實現Java_cc_ccbu_jnitest_Test_textFromJni方法即可。

2.4 動態註冊

對應與上面的靜態註冊方法,還有一種動態註冊JNI函數的方式,即動態註冊。動態註冊是當Java層調用System.loadLibrary方法加載so庫後,本地庫的JNI_OnLoad函數會被調用,在JNI_OnLoad函數中通過調用RegisterNatives函數來完成本地方法的註冊。

本地so庫加載和卸載時,會調用JNI_OnLoad和JNI_OnUnload函數,函數原型如下:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);

動態註冊本地方法的函數RegisterNatives原型如下:

jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

其中JNINativeMethod結構體用來描述本地方法結構,其定義如下:

typedef struct {
    const char* name;      // Java方法名
    const char* signature; // Java方法簽名
    void* fnPtr;           // jni本地方法對應的函數指針
} JNINativeMethod;

下面通過一個例子來展示jni動態註冊本地方法的過程。還是以上面靜態註冊的Test類爲例

  1. 在Java文件中定義本地方法,加載本地so庫

    package cc.ccbu.jnitest;
    
    public class Test {
        static {
            System.loadLibrary("native-lib");
        }
    
        public native String textFromJni();
    }
    
  2. 在JNI_OnLoad函數中註冊本地方法

    jstring textFromJni(JNIEnv* env, jobject thiz) {
        return env->NewStringUTF("text from jni");
    }
    
    static JNINativeMethod gMethods[] = {
            {"textFromJni", "()Ljava/lang/String;", (void*)textFromJni}
    };
    
    int registerMethod(JNIEnv *env) {
        jclass test = env->FindClass("cc/ccbu/jnitest/Test");
        return env->RegisterNatives(test, gMethods, sizeof(gMethods)/ sizeof(gMethods[0]));
    }
    
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env = NULL;
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
            return JNI_ERR;
        }
    
        if (registerMethod(env) != JNI_OK) {
            return JNI_ERR;
        }
    
        return  JNI_VERSION_1_6;
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章