Android JNI開發(fā)詳解(2)-函數(shù)注冊(cè)

1. JNI開發(fā)流程

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

  2. 創(chuàng)建Java層本地接口調(diào)用類,并定義好相應(yīng)的本地函數(shù)。

  3. 將Java源代碼編譯成class字節(jié)碼文件(Android studio會(huì)自動(dòng)生成)。

  4. 創(chuàng)建對(duì)應(yīng)的本地函數(shù)接口,并注冊(cè)本地函數(shù),Jni函數(shù)注冊(cè)有兩種方式,靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè),靜態(tài)注冊(cè)使用javah工具自動(dòng)生成對(duì)應(yīng)的本地接口函數(shù)定義,動(dòng)態(tài)注冊(cè)則需要在JNI_OnLoad函數(shù)中調(diào)用RegisterNatives來(lái)完成注冊(cè)。

  5. 實(shí)現(xiàn)本地函數(shù)接口函數(shù)的具體功能實(shí)現(xiàn)。

  6. 修改CMakeLists.txtAndroid.mk。

  7. 編譯測(cè)試。

2. JNI函數(shù)注冊(cè)

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

JNI技術(shù)是Java世界與Native世界的通信橋梁,具體到代碼,Java層的代碼如何同Native層的代碼進(jìn)行調(diào)用的呢?我們都知道,在調(diào)用native方法之前,首先要調(diào)用System.loadLibrary接口加載一個(gè)實(shí)現(xiàn)了native方法的動(dòng)態(tài)庫(kù)才能正常訪問,否則就會(huì)拋出java.lang.UnsatisfiedLinkError異常 。 那么,在Java中調(diào)用某個(gè)native方法時(shí),JVM是通過什么方式,能正確的找到動(dòng)態(tài)庫(kù)中C/C++實(shí)現(xiàn)的那個(gè)native函數(shù)呢?

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

  1. 按照J(rèn)NI規(guī)范的命名規(guī)則。

  2. 調(diào)用JNI提供的RegisterNatives函數(shù),將本地函數(shù)注冊(cè)到JVM中。

第一種方式,可用使用javah工具按照J(rèn)ava類中定義的native方法,按照J(rèn)NI規(guī)范的命名規(guī)則的方式自動(dòng)生成Jni本地C/C++頭文件。第二種方式則需要在本地庫(kù)的JNI_OnLoad函數(shù)中調(diào)用RegisterNatives來(lái)動(dòng)態(tài)注冊(cè)。

JNI函數(shù)注冊(cè)是將Java層聲明的Native方法同實(shí)際的Native函數(shù)綁定起來(lái)的實(shí)現(xiàn)方式,也就是說(shuō),只要通過JNI函數(shù)注冊(cè)機(jī)制注冊(cè)了本地方,Java層就可以直接調(diào)用定義的這些本地方法了。對(duì)應(yīng)上述JVM查找native方法的兩種方式,JNI函數(shù)注冊(cè)方式一般分為靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)兩種方式。

2.2 javah工具和javap工具

為了方便我們進(jìn)行完成注冊(cè)操作,我們經(jīng)常還會(huì)用到j(luò)avah和javap兩個(gè)命令行工具。

  • javah工具用于生產(chǎn)本地方法頭文件

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

    在JDK1.6中,在bin/classes目錄下執(zhí)行

    執(zhí)行前需要先編譯通過(編譯為class文件),大多數(shù)IDE會(huì)自動(dòng)執(zhí)行編譯,因此不需要在手動(dòng)進(jìn)行編譯

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

2.3 靜態(tài)注冊(cè)

靜態(tài)注冊(cè)實(shí)際十分簡(jiǎn)單,就是使用上面提到的javah工具為我們生產(chǎn)本地方法頭文件,然后在根據(jù)生成的頭文件完成相應(yīng)的函數(shù)即可。靜態(tài)注冊(cè)即本地函數(shù)按照特定的命名規(guī)則命名本地方法,使得這些本地方法與Java類中定義的本地方法一一對(duì)應(yīng),在執(zhí)行JNI調(diào)用時(shí),只需要根據(jù)命名規(guī)則去調(diào)用so庫(kù)中對(duì)應(yīng)的方法即可。

下面時(shí)一個(gè)使用靜態(tài)注冊(cè)的例子,Test類定義了Java中的本地方法。

package cc.ccbu.jnitest;

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

    public native String textFromJni();
}

使用javah生成對(duì)應(yīng)的本地方法頭文件。

#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文件實(shí)現(xiàn)Java_cc_ccbu_jnitest_Test_textFromJni方法即可。

2.4 動(dòng)態(tài)注冊(cè)

對(duì)應(yīng)與上面的靜態(tài)注冊(cè)方法,還有一種動(dòng)態(tài)注冊(cè)JNI函數(shù)的方式,即動(dòng)態(tài)注冊(cè)。動(dòng)態(tài)注冊(cè)是當(dāng)Java層調(diào)用System.loadLibrary方法加載so庫(kù)后,本地庫(kù)的JNI_OnLoad函數(shù)會(huì)被調(diào)用,在JNI_OnLoad函數(shù)中通過調(diào)用RegisterNatives函數(shù)來(lái)完成本地方法的注冊(cè)。

本地so庫(kù)加載和卸載時(shí),會(huì)調(diào)用JNI_OnLoad和JNI_OnUnload函數(shù),函數(shù)原型如下:

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

動(dòng)態(tài)注冊(cè)本地方法的函數(shù)RegisterNatives原型如下:

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

其中JNINativeMethod結(jié)構(gòu)體用來(lái)描述本地方法結(jié)構(gòu),其定義如下:

typedef struct {
    const char* name;      // Java方法名
    const char* signature; // Java方法簽名
    void* fnPtr;           // jni本地方法對(duì)應(yīng)的函數(shù)指針
} JNINativeMethod;

下面通過一個(gè)例子來(lái)展示jni動(dòng)態(tài)注冊(cè)本地方法的過程。還是以上面靜態(tài)注冊(cè)的Test類為例

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

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

    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;
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容