Android NDK開發(fā):JNI基礎(chǔ)篇

注:原文地址

1. JNI 概念

1.1 概念

JNI 全稱 Java Native Interface,Java 本地化接口,可以通過 JNI 調(diào)用系統(tǒng)提供的 API。操作系統(tǒng),無論是 Linux,Windows 還是 Mac OS,或者一些匯編語言寫的底層硬件驅(qū)動都是 C/C++ 寫的。Java和C/C++不同 ,它不會直接編譯成平臺機(jī)器碼,而是編譯成虛擬機(jī)可以運(yùn)行的Java字節(jié)碼的.class文件,通過JIT技術(shù)即時編譯成本地機(jī)器碼,所以有效率就比不上C/C++代碼,JNI技術(shù)就解決了這一痛點,JNI 可以說是 C 語言和 Java 語言交流的適配器、中間件,下面我們來看看JNI調(diào)用示意圖:來自JNI開發(fā)系列①JNI概念及開發(fā)流程 - 簡書

JNI 調(diào)用示意圖

JNI技術(shù)通過JVM調(diào)用到各個平臺的API,雖然JNI可以調(diào)用C/C++,但是JNI調(diào)用還是比C/C++編寫的原生應(yīng)用還是要慢一點,不過對高性能計算來說,這點算不得什么,享受它的便利,也要承擔(dān)它的弊端。

1.2 JNI 與 NDK 區(qū)別

  • JNI:JNI是一套編程接口,用來實現(xiàn)Java代碼與本地的C/C++代碼進(jìn)行交互;
  • NDK: NDK是Google開發(fā)的一套開發(fā)和編譯工具集,可以生成動態(tài)鏈接庫,主要用于Android的JNI開發(fā);

2. JNI 作用

  • 擴(kuò)展:JNI擴(kuò)展了JVM能力,驅(qū)動開發(fā),例如開發(fā)一個wifi驅(qū)動,可以將手機(jī)設(shè)置為無限路由;
  • 高效: 本地代碼效率高,游戲渲染,音頻視頻處理等方面使用JNI調(diào)用本地代碼,C語言可以靈活操作內(nèi)存;
  • 復(fù)用: 在文件壓縮算法 7zip開源代碼庫,機(jī)器視覺 OpenCV開放算法庫等方面可以復(fù)用C平臺上的代碼,不必在開發(fā)一套完整的Java體系,避免重復(fù)發(fā)明輪子;
  • 特殊: 產(chǎn)品的核心技術(shù)一般也采用JNI開發(fā),不易破解;

JNI在Android中作用:
JNI可以調(diào)用本地代碼庫(即C/C++代碼),并通過 Dalvik 虛擬機(jī)與應(yīng)用層和應(yīng)用框架層進(jìn)行交互,Android中JNI代碼主要位于應(yīng)用層和應(yīng)用框架層;

  • 應(yīng)用層: 該層是由JNI開發(fā),主要使用標(biāo)準(zhǔn)JNI編程模型;
  • 應(yīng)用框架層: 使用的是Android中自定義的一套JNI編程模型,該自定義的JNI編程模型彌補(bǔ)了標(biāo)準(zhǔn)JNI編程模型的不足;

補(bǔ)充知識點:

Java語言執(zhí)行流程:

  • 編譯字節(jié)碼:Java編譯器編譯 .java源文件,獲得.class 字節(jié)碼文件;
  • 裝載類庫:使用類裝載器裝載平臺上的Java類庫,并進(jìn)行字節(jié)碼驗證;
  • Java虛擬機(jī):將字節(jié)碼加入到JVM中,Java解釋器和即時編譯器同時處理字節(jié)碼文件,將處理后的結(jié)果放入運(yùn)行時系統(tǒng);
  • 調(diào)用JVM所在平臺類庫:JVM處理字節(jié)碼后,轉(zhuǎn)換成相應(yīng)平臺的操作,調(diào)用本平臺底層類庫進(jìn)行相關(guān)處理;
Java 語言執(zhí)行流程

Java一次編譯到處執(zhí)行: JVM在不同的操作系統(tǒng)都有實現(xiàn),Java可以一次編譯到處運(yùn)行,字節(jié)碼文件一旦編譯好了,可以放在任何平臺的虛擬機(jī)上運(yùn)行;

3. 查看 jni.h 文件源碼方法

jni.h 頭文件就是為了讓 C/C++ 類型和 Java 原始類型相匹配的頭文件定義。

可以通過點擊 Android項目的含有#include <jni.h>的頭文件或 C/C++ 文件跳轉(zhuǎn)到 jni.h 頭文件查看;
如果沒有這樣的文件的話,可以在 Android Studio 上新建一個類,隨便寫一個 native 方法,然后點擊紅色的方法,AS 會自動生成一個對應(yīng)的 C 語言文件jnitest.c,就可以找到 jni.h 文件了

或者,通過 javah 命令javah cn.cfanr.testjni.JniTest,就可以生成對應(yīng)頭文件cn_cfanr_testjni_JniTest.h

javah 生成的

4. JNI 數(shù)據(jù)類型映射

由頭文件代碼可以看到,jni.h有很多類型預(yù)編譯的定義,并且區(qū)分了 C 和 C++的不同環(huán)境。

#ifdef HAVE_INTTYPES_H
# include <inttypes.h>      /* C99 */
typedef uint8_t         jboolean;       /* unsigned 8 bits */
typedef int8_t          jbyte;          /* signed 8 bits */
typedef uint16_t        jchar;          /* unsigned 16 bits */
typedef int16_t         jshort;         /* signed 16 bits */
typedef int32_t         jint;           /* signed 32 bits */
typedef int64_t         jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#else
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#endif

/* "cardinal indices and sizes" */
typedef jint            jsize;

#ifdef __cplusplus
/*
 * 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 {};
//……

typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
//……

#else /* not __cplusplus */

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
//……

#endif

當(dāng)是C++環(huán)境時,jobject, jclass, jstring, jarray 等都是繼承自_jobject類,而在 C 語言環(huán)境是,則它的本質(zhì)都是空類型指針typedef void* jobject;

4.1 基本數(shù)據(jù)類型

下圖是Java基本數(shù)據(jù)類型和本地類型的映射關(guān)系,這些基本數(shù)據(jù)類型都是可以直接在 Native 層直接使用的

基本數(shù)據(jù)類型映射

4.2 引用數(shù)據(jù)類型

另外,還有引用數(shù)據(jù)類型和本地類型的映射關(guān)系:

引用數(shù)據(jù)類型映射

需要注意的是,

  • 1)引用類型不能直接在 Native 層使用,需要根據(jù) JNI 函數(shù)進(jìn)行類型的轉(zhuǎn)化后,才能使用;
  • 2)多維數(shù)組(含二維數(shù)組)都是引用類型,需要使用 jobjectArray 類型存取其值;

例如,二維整型數(shù)組就是指向一位數(shù)組的數(shù)組,其聲明使用方式如下:

      //獲得一維數(shù)組的類引用,即jintArray類型  
    jclass intArrayClass = env->FindClass("[I");   
    //構(gòu)造一個指向jintArray類一維數(shù)組的對象數(shù)組,該對象數(shù)組初始大小為length,類型為 jsize
    jobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL); 

4.3 方法和變量 ID

同樣不能直接在 Native 層使用。當(dāng) Native 層需要調(diào)用 Java 的某個方法時,需要通過 JNI 函數(shù)獲取它的 ID,根據(jù) ID 調(diào)用 JNI 函數(shù)獲取該方法;變量的獲取也是類似。ID 的結(jié)構(gòu)體如下:

struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */

struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */

5. JNI 描述符

5.1域描述符

1)基本類型描述符

下面是基本的數(shù)據(jù)類型的描述符,除了 boolean 和 long 類型分別是 Z 和 J 外,其他的描述符對應(yīng)的都是Java類型名的大寫首字母。另外,void 的描述符為 V

基本類型描述符

2)引用類型描述符

一般引用類型描述符的規(guī)則如下,注意不要丟掉“;”

L + 類描述符 + ;

如,String 類型的域描述符為:

Ljava/lang/String;

數(shù)組的域描述符特殊一點,如下,其中有多少級數(shù)組就有多少個“[”,數(shù)組的類型為類時,則有分號,為基本類型時沒有分號

[ + 其類型的域描述符

例如:

int[]    描述符為 [I
double[] 描述符為 [D
String[] 描述符為 [Ljava/lang/String;
Object[] 描述符為 [Ljava/lang/Object;
int[][]  描述符為 [[I
double[][] 描述符為 [[D

對應(yīng)在 jni.h 獲取 Java 的字段的 native 函數(shù)如下,name為 Java 的字段名字,sig 為域描述符

//C
jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//C++
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
    { return functions->GetObjectField(this, obj, fieldID); }

具體使用,后面會講到

5.2 類描述符

類描述符是類的完整名稱:包名+類名,java 中包名用 . 分割,jni 中改為用 / 分割
如,Java 中 java.lang.String 類的描述符為 java/lang/String
native 層獲取 Java 的類對象,需要通過 FindClass() 函數(shù)獲取, jni.h 的函數(shù)定義如下:

//C
jclass  (*FindClass)(JNIEnv*, const char*);
//C++
jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

字符串參數(shù)就是類的引用類型描述符,如 Java 對象 cn.cfanr.jni.JniTest,對應(yīng)字符串為Lcn/cfanr/jni/JniTest; 如下:

jclass jclazz = env->FindClass("Lcn/cfanr/jni/JniTest;");

詳細(xì)用法的例子,后面會講到。

5.3 方法描述符

方法描述符需要將所有參數(shù)類型的域描述符按照聲明順序放入括號,然后再加上返回值類型的域描述符,其中沒有參數(shù)時,不需要括號,如下規(guī)則:

(參數(shù)……)返回類型

例如:

  Java 層方法   ——>  JNI 函數(shù)簽名
String getString()  ——>  Ljava/lang/String;
int sum(int a, int b)  ——>  (II)I
void main(String[] args) ——> ([Ljava/lang/String;)V

另外,對應(yīng)在 jni.h 獲取 Java 方法的 native 函數(shù)如下,其中 jclass 是獲取到的類對象,name 是 Java 對應(yīng)的方法名字,sig 就是上面說的方法描述符

//C
jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//C++
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

不過在實際編程中,如果使用 javah 工具來生成對應(yīng)的 native 代碼,就不需要手動編寫對應(yīng)的類型轉(zhuǎn)換了。

6. JNIEnv 分析

JNIEnv 是 jni.h 文件最重要的部分,它的本質(zhì)是指向函數(shù)表指針的指針(JavaVM也是),函數(shù)表里面定義了很多 JNI 函數(shù),同時它也是區(qū)分 C 和 C++環(huán)境的(由上面介紹描述符時也可以看到),在 C 語言環(huán)境中,JNIEnv 是strut JNINativeInterface*的指針別名。

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)  
typedef _JNIEnv JNIEnv;   //C++中的 JNIEnv 類型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;  //C語言的 JNIEnv 類型
typedef const struct JNIInvokeInterface* JavaVM;
#endif

6.1 JNIEnv 特點

  • JNIEnv 是一個指針,指向一組 JNI 函數(shù),通過這些函數(shù)可以實現(xiàn) Java 層和 JNI 層的交互,就是說通過 JNIEnv 調(diào)用 JNI 函數(shù)可以訪問 Java 虛擬機(jī),操作 Java 對象;
  • 所有本地函數(shù)都會接收 JNIEnv 作為第一個參數(shù);(不過 C++ 的JNI 函數(shù)已經(jīng)對 JNIEnv 參數(shù)進(jìn)行了封裝,不用寫在函數(shù)參數(shù)上)
  • 用作線程局部存儲,不能在線程間共享一個 JNIEnv 變量,也就是說 JNIEnv 只在創(chuàng)建它的線程有效,不能跨線程傳遞;相同的 Java 線程調(diào)用本地方法,所使用的 JNIEnv 是相同的,一個 native 方法不能被不同的 Java 線程調(diào)用;

6.2 JavaEnv 和 JavaVM 的關(guān)系

JNIEnv和Dalvik的JavaVM的關(guān)系-CSDN

  • 1)每個進(jìn)程只有一個 JavaVM(理論上一個進(jìn)程可以擁有多個 JavaVM 對象,但 Android 只允許一個),每個線程都會有一個 JNIEnv,大部分 JNIAPI 通過 JNIEnv 調(diào)用;也就是說,JNI 全局只有一個 JavaVM,而可能有多個 JNIEnv;
  • 2)一個 JNIEnv 內(nèi)部包含一個 Pointer,Pointer 指向 Dalvik 的 JavaVM 對象的 Function Table,JNIEnv 內(nèi)部的函數(shù)執(zhí)行環(huán)境來源于 Dalvik 虛擬機(jī);
  • 3)Android 中每當(dāng)一個Java 線程第一次要調(diào)用本地 C/C++ 代碼時,Dalvik 虛擬機(jī)實例會為該 Java 線程產(chǎn)生一個 JNIEnv 指針;
  • 4)Java 每條線程在和 C/C++ 互相調(diào)用時,JNIEnv 是互相獨立,互不干擾的,這樣就提升了并發(fā)執(zhí)行時的安全性;
  • 5)當(dāng)本地的 C/C++ 代碼想要獲得當(dāng)前線程所想要使用的 JNIEnv 時,可以使用 Dalvik VM 對象的 JavaVM* jvm->GetEnv()方法,該方法會返回當(dāng)前線程所在的 JNIEnv*;
  • 6)Java 的 dex 字節(jié)碼和 C/C++ 的 .so 同時運(yùn)行 Dalvik VM 之內(nèi),共同使用一個進(jìn)程空間;

6.3 C 語言的 JNIEnv

由上面代碼可知,C 語言的JNIEnv 就是const struct JNINativeInterface*,而 JNIEnv* env就等價于JNINativeInterface** env,env 實際是一個二級指針,所以想要得到 JNINativeInterface 結(jié)構(gòu)體中定義的函數(shù)指針,就需要先獲取 JNINativeInterface 的一級指針對象*env,然后才能通過一級指針對象調(diào)用 JNI 函數(shù),例如:
(*env)->NewStringUTF(env, "hello")

struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jint        (*GetVersion)(JNIEnv *);
    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize);
    jclass      (*FindClass)(JNIEnv*, const char*);
    jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);
    jfieldID    (*FromReflectedField)(JNIEnv*, jobject);
    /* spec doesn't show jboolean parameter */
    jobject     (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
    jclass      (*GetSuperclass)(JNIEnv*, jclass);
    jboolean    (*IsAssignableFrom)(JNIEnv*, jclass, jclass);
    /* spec doesn't show jboolean parameter */
    jobject     (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);
      //……定義了一系列關(guān)于 Java 操作的函數(shù)
}

6.4 C++的 JNIEnv

typedef _JNIEnv JNIEnv;可知,C++的 JNIEnv 是 _JNIEnv 結(jié)構(gòu)體,而 _JNIEnv 結(jié)構(gòu)體定義了 JNINativeInterface 的結(jié)構(gòu)體指針,內(nèi)部定義的函數(shù)實際上是調(diào)用 JNINativeInterface 的函數(shù),所以C++的 env 是一級指針,調(diào)用時不需要加 env 作為函數(shù)的參數(shù),例如:env->NewStringUTF(env, "hello")

struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
#if defined(__cplusplus)
    jint GetVersion()
    { return functions->GetVersion(this); }

    jclass DefineClass(const char *name, jobject loader, const jbyte* buf, jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }

    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

    jmethodID FromReflectedMethod(jobject method)
    { return functions->FromReflectedMethod(this, method); }

    jfieldID FromReflectedField(jobject field)
    { return functions->FromReflectedField(this, field); }

    jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic)
    { return functions->ToReflectedMethod(this, cls, methodID, isStatic); }

    jclass GetSuperclass(jclass clazz)
    { return functions->GetSuperclass(this, clazz); }
    //……
}

7. JNI 的兩種注冊方式

Java 的 native 方法是如何鏈接 C/C++中的函數(shù)的呢?可以通過靜態(tài)和動態(tài)的方式注冊JNI。

7.1靜態(tài)注冊

原理:根據(jù)函數(shù)名建立 Java 方法和 JNI 函數(shù)的一一對應(yīng)關(guān)系。流程如下:

  • 先編寫 Java 的 native 方法;
  • 然后用 javah 工具生成對應(yīng)的頭文件,執(zhí)行命令 javah packagename.classname可以生成由包名加類名命名的 jni 層頭文件,或執(zhí)行命名javah -o custom.h packagename.classname,其中 custom.h 為自定義的文件名;
  • 實現(xiàn) JNI 里面的函數(shù),再在Java中通過System.loadLibrary加載 so 庫即可;

靜態(tài)注冊的方式有兩個重要的關(guān)鍵詞 JNIEXPORT 和 JNICALL,這兩個關(guān)鍵詞是宏定義,主要是注明該函數(shù)式 JNI 函數(shù),當(dāng)虛擬機(jī)加載 so 庫時,如果發(fā)現(xiàn)函數(shù)含有這兩個宏定義時,就會鏈接到對應(yīng)的 Java 層的 native 方法。

由前面3. 查看 jni.h 文件源碼方法生成頭文件的方法,重新創(chuàng)建一個cn.cfanr.test_jni.Jni_Test.java的類

public class Jni_Test {
    private static native int swap();

    private static native void swap(int a, int b);

    private static native void swap(String a, String b);

    private native void swap(int[] arr, int a, int b);

    private static native void swap_0(int a, int b);
}

用 javah 工具生成以下頭文件:

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

#ifndef _Included_cn_cfanr_test_jni_Jni_Test
#define _Included_cn_cfanr_test_jni_Jni_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     cn_cfanr_test_jni_Jni_Test
 * Method:    swap
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__
  (JNIEnv *, jclass);    // 凡是重載的方法,方法后面都會多一個下劃線

/*
 * Class:     cn_cfanr_test_jni_Jni_Test
 * Method:    swap
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__II
  (JNIEnv *, jclass, jint, jint);

/*
 * Class:     cn_cfanr_test_jni_Jni_Test
 * Method:    swap
 * Signature: (Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__Ljava_lang_String_2Ljava_lang_String_2
  (JNIEnv *, jclass, jstring, jstring);

/*
 * Class:     cn_cfanr_test_jni_Jni_Test
 * Method:    swap
 * Signature: ([III)V
 */
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap___3III
  (JNIEnv *, jobject, jintArray, jint, jint);  // 非 static 的為 jobject

/*
 * Class:     cn_cfanr_test_jni_Jni_Test
 * Method:    swap_0
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap_10   
  (JNIEnv *, jclass, jint, jint);   // 不知道為什么后面沒有 II

#ifdef __cplusplus
}
#endif
#endif

可以看出 JNI 的調(diào)用函數(shù)的定義是按照一定規(guī)則命名的:
JNIEXPORT 返回值 JNICALL Java_全路徑類名_方法名_參數(shù)簽名(JNIEnv* , jclass, 其它參數(shù));
其中 Java_ 是為了標(biāo)識該函數(shù)來源于 Java。經(jīng)檢驗(不一定正確),如果是重載的方法,則有“參數(shù)簽名”,否則沒有;另外如果使用的是 C++,在函數(shù)前面加上 extern “C”(表示按照 C 的方式編譯),函數(shù)命名后面就不需要加上“參數(shù)簽名”。

另外還需要注意幾點特殊規(guī)則:(參考:官方JNI規(guī)范翻譯 | linlinjava的博客 2.2.1 本地方法名解析

  • 1. 包名或類名或方法名中含下劃線 _ 要用 _1 連接;
  • 2. 重載的本地方法命名要用雙下劃線 __ 連接;
  • 3. 參數(shù)簽名的斜杠 “/” 改為下劃線 “_” 連接,分號 “;” 改為 “_2” 連接,左方括號 “[” 改為 “_3” 連接;
    另外,對于 Java 的 native 方法,static 和非 static 方法的區(qū)別在于第二個參數(shù),static 的為 jclass,非 static 的 為 jobject;JNI 函數(shù)中是沒有修飾符的。

優(yōu)點:
實現(xiàn)比較簡單,可以通過 javah 工具將 Java代碼的 native 方法直接轉(zhuǎn)化為對應(yīng)的native層代碼的函數(shù);
缺點:

  • javah 生成的 native 層函數(shù)名特別長,可讀性很差;
  • 后期修改文件名、類名或函數(shù)名時,頭文件的函數(shù)將失效,需要重新生成或手動改,比較麻煩;
  • 程序運(yùn)行效率低,首次調(diào)用 native 函數(shù)時,需要根據(jù)函數(shù)名在 JNI 層搜索對應(yīng)的本地函數(shù),建立對應(yīng)關(guān)系,有點耗時;

7.2 動態(tài)注冊

原理:直接告訴 native 方法其在JNI 中對應(yīng)函數(shù)的指針。通過使用 JNINativeMethod 結(jié)構(gòu)來保存 Java native 方法和 JNI 函數(shù)關(guān)聯(lián)關(guān)系,步驟:

  • 先編寫 Java 的 native 方法;
  • 編寫 JNI 函數(shù)的實現(xiàn)(函數(shù)名可以隨便命名);
  • 利用結(jié)構(gòu)體 JNINativeMethod 保存Java native方法和 JNI函數(shù)的對應(yīng)關(guān)系;
  • 利用registerNatives(JNIEnv* env)注冊類的所有本地方法;
  • 在 JNI_OnLoad 方法中調(diào)用注冊方法;
  • 在Java中通過System.loadLibrary加載完JNI動態(tài)庫之后,會調(diào)用JNI_OnLoad函數(shù),完成動態(tài)注冊;
//JNINativeMethod結(jié)構(gòu)體
typedef struct {
    const char* name;       //Java中native方法的名字
    const char* signature;  //Java中native方法的描述符
    void*       fnPtr;      //對應(yīng)JNI函數(shù)的指針
} JNINativeMethod;

/**
 * @param clazz java類名,通過 FindClass 獲取
 * @param methods JNINativeMethod 結(jié)構(gòu)體指針
 * @param nMethods 方法個數(shù)
 */
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)

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

由于篇幅原因,具體的靜態(tài)注冊、動態(tài)注冊、數(shù)據(jù)類型映射和描述符的練習(xí)放到下一篇文章:Android NDK開發(fā):JNI實戰(zhàn)篇

注:文章通過閱讀 JNI 的文檔和參照網(wǎng)上的博客總結(jié)出來的,如有錯誤,還望指出!

參考:
JNI開發(fā)系列①JNI概念及開發(fā)流程 - 簡書
JNI 數(shù)據(jù)類型映射、域描述符說明 - qinjuning - CSDN博客
Android JNI 之 JNIEnv 解析 - 韓曙亮 - CSDN博客
Android 開發(fā) 之 JNI入門 - NDK從入門到精通 -韓曙亮 - CSDN博客
JNI 兩種注冊過程實戰(zhàn) - Android - 掘金
Andoid NDK編程 注冊native函數(shù) // Coding Life

擴(kuò)展閱讀:
JNI 使用指南-胡凱
JNI 常用函數(shù)大全 qinjuning- CSDN博客
Android JNI原理分析 - Gityuan博客 | 袁輝輝博客
JNI API 文檔 (Java8): Java Native Interface Specification
官方JNI API 規(guī)范翻譯 | linlinjava的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容