JNI-入門之二

 

Android中JNI編程的那些事兒html

首先說明,Android系統不容許一個純粹使用C/C++的程序出現,它要求必須是經過Java代碼嵌入Native C/C++——即經過JNI的方式來使用本地(Native)代碼。所以JNI對Android底層開發人員很是重要。java

如何將.so文件打包到.APKandroid

讓咱們 先 從最簡單的狀況開始,假如已有一個JNI實現——libxxx.so文件,那麼如何在APK中使用它呢?c++

在我最初寫相似程序的時候,我會將libxxx.so文件push到/system/lib/目錄下,而後在Java代碼中執行 System.loadLibrary(xxx),這是個可行的作法,但須要取得/system/lib 目錄 的寫權限(模擬器經過adb remount取得該權限)。但模擬器 重啓之 後libxxx.so文件會消失。如今 我找到了更好的方法,把.so文件打包到apk中分發給最終用 戶,不論是模擬器 或者 真機 ,都再也不須要system分區的寫權限。實現步驟以下:git

一、在你的項目根目錄下創建libs/armeabi目錄;程序員

二、將libxxx.so文件copy到 libs/armeabi/下;編程

三、此時ADT插件自動編譯輸出的.apk文件中已經包括.so文件了;windows

四、安裝APK文件,便可直接使用JNI中的方法;數組

我想還須要簡單說明一下libxxx.so的命名規則,沿襲Linux傳統,lib<something>.so是類庫文件名稱的格 式,但在Java的System.loadLibrary(「 something 」)方法中指定庫名稱時,不能包括 前綴—— lib,以及後綴—— .so。oracle

準備編寫本身的JNI模塊

你必定想知道如何編寫本身的xxx.so,不過這涉及了太多有關JNI的知識。簡單的說:JNI是Java平臺定義的用於和宿主平臺上的本地代碼進 行交互的「Java標準」,它一般有兩個使用場景:1.使用(以前使用c/c++、delphi開發的)遺留代碼;2.爲了更好、更直接地與硬件交互 並 得到更高性能 。你能夠經過如下連接瞭解JNI的更多資料:

JNI之Hello World

一、首先建立含有native方法的Java類:

  1. package com.okwap.testjni;     
  2.   
  3. public final class MyJNI {   
  4. //native方法,   
  5.     public static native String sayHello(String name);   
  6. }  
  1. package com.okwap.testjni;    
  2.   
  3. public final class MyJNI {  
  4. //native方法,  
  5.     public static native String sayHello(String name);  
  6. }  

二、經過javah命令生成.h文件,內容以下(com_okwap_testjni.h文件):

  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <JNI.H>   
  3. /* Header for class com_okwap_testjni_MyJNI */    
  4.   
  5. #ifndef _Included_com_okwap_testjni_MyJNI    
  6.   
  7. #define _Included_com_okwap_testjni_MyJNI    
  8.   
  9. #ifdef __cplusplus    
  10.   
  11. extern "C" {    
  12.   
  13. #endif    
  14.   
  15. /*  
  16.  
  17.  * Class:     com_okwap_testjni_MyJNI  
  18.  
  19.  * Method:    sayHello  
  20.  
  21.  * Signature: (Ljava/lang/String;)Ljava/lang/String;  
  22.  
  23.  */    
  24.   
  25. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello    
  26.   
  27.   (JNIEnv *, jclass, jstring);    
  28.   
  29. #ifdef __cplusplus    
  30.   
  31. }    
  32.   
  33. #endif   
  34. #endif  
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_okwap_testjni_MyJNI */   
  4.   
  5. #ifndef _Included_com_okwap_testjni_MyJNI   
  6.   
  7. #define _Included_com_okwap_testjni_MyJNI   
  8.   
  9. #ifdef __cplusplus   
  10.   
  11. extern "C" {   
  12.   
  13. #endif   
  14.   
  15. /* 
  16.  
  17.  * Class:     com_okwap_testjni_MyJNI 
  18.  
  19.  * Method:    sayHello 
  20.  
  21.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
  22.  
  23.  */   
  24.   
  25. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello   
  26.   
  27.   (JNIEnv *, jclass, jstring);   
  28.   
  29. #ifdef __cplusplus   
  30.   
  31. }   
  32.   
  33. #endif  
  34. #endif  
  35. </jni.h>  

這是一個標準的C語言頭文件,其中的JNIEXPORT、JNICALL是JNI關鍵字(事實上它是沒有任何內容的宏,僅用於指示性說明),而 jint、jstring是JNI環境下對int及java.lang.String類型的映射。這些關鍵字的定義均可以在jni.h中看到。
三、在 com_okwap_testjni.c文件中實現以上方法:

  1. #include <STRING.H>   
  2. #include <JNI.H>   
  3. #include "com_okwap_testjni.h"     
  4.   
  5. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello(JNIEnv* env, jclass, jstring str){   
  6.     //從jstring類型取得c語言環境下的char*類型   
  7. const char* name = (*env)->GetStringUTFChars(env, str, 0);   
  8. //本地常量字符串   
  9. char* hello = "你好,";   
  10. //動態分配目標字符串空間   
  11. char* result = malloc((strlen(name) + strlen(hello) + 1)*sizeof(char));   
  12. memset(result,0,sizeof(result));   
  13. //字符串連接   
  14. strcat(result,hello);   
  15. strcat(result,name);   
  16. //釋放jni分配的內存   
  17. (*env)->ReleaseStringUTFChars(env,str,name);   
  18. //生成返回值對象   
  19. str = (*env)->NewStringUTF(env, "你好 JNI~!");   
  20. //釋放動態分配的內存   
  21. free(result);     
  22.   
  23. //   
  24. return str;   
  25. }  
  1. #include <string.h>  
  2. #include <jni.h>  
  3. #include "com_okwap_testjni.h"    
  4.   
  5. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello(JNIEnv* env, jclass, jstring str){  
  6.     //從jstring類型取得c語言環境下的char*類型  
  7. const char* name = (*env)->GetStringUTFChars(env, str, 0);  
  8. //本地常量字符串  
  9. char* hello = "你好,";  
  10. //動態分配目標字符串空間  
  11. char* result = malloc((strlen(name) + strlen(hello) + 1)*sizeof(char));  
  12. memset(result,0,sizeof(result));  
  13. //字符串連接  
  14. strcat(result,hello);  
  15. strcat(result,name);  
  16. //釋放jni分配的內存  
  17. (*env)->ReleaseStringUTFChars(env,str,name);  
  18. //生成返回值對象  
  19. str = (*env)->NewStringUTF(env, "你好 JNI~!");  
  20. //釋放動態分配的內存  
  21. free(result);    
  22.   
  23. //  
  24. return str;  
  25. }  
  26. </jni.h></string.h>  

四、編譯——兩種不一樣的編譯環境
以上的C語言代碼要編譯成最終.so動態庫文件,有兩種途徑:
Android NDK :全稱是Native Developer Kit,是用於編譯本地JNI源碼的工具,爲開發人員將本地方法整合到Android應用中提供了方便。事實上NDK和完整源碼編譯環境同樣,都使用 Android的編譯系統——即經過Android.mk文件控制編譯。NDK能夠運行在Linux、Mac、Window(+cygwin)三個平臺 上。有關NDK的使用方法及更多細節請參考如下資料:
eoe特刊第七期《NDK總結》http://blog.eoemobile.com/?p=27
http://androidappdocs.appspot.com/sdk/ndk/index.html ;
完整源碼編譯環境 :Android平臺提供有基於make的編譯系統,爲App編寫正確的Android.mk文件就可以使用該編譯系統。該環境須要經過git從官方網站獲 取完整源碼副本併成功編譯,更多細節請參考:http://source.android.com/index.html
無論你選擇以上兩種方法的哪個,都必須編寫本身的Android.mk文件,有關該文件的編寫請參考相關文檔。
JNI組件的入口函數——JNI_OnLoad()、JNI_OnUnload()
JNI組件被成功加載和卸載時,會進行函數回調,當VM執行到System.loadLibrary(xxx)函數時,首先會去執行JNI組件中的JNI_OnLoad()函數,而當VM釋放該組件時會呼叫JNI_OnUnload()函數。先看示例代碼:

  1. //onLoad方法,在System.loadLibrary()執行時被調用   
  2. jint JNI_OnLoad(JavaVM* vm, void* reserved){   
  3.     LOGI("JNI_OnLoad startup~~!");   
  4.         return JNI_VERSION_1_4;   
  5. }        
  6.   
  7. //onUnLoad方法,在JNI組件被釋放時調用   
  8. void JNI_OnUnload(JavaVM* vm, void* reserved){   
  9.     LOGE("call JNI_OnUnload ~~!!");   
  10. }  
  1. //onLoad方法,在System.loadLibrary()執行時被調用  
  2. jint JNI_OnLoad(JavaVM* vm, void* reserved){  
  3.     LOGI("JNI_OnLoad startup~~!");  
  4.         return JNI_VERSION_1_4;  
  5. }       
  6.   
  7. //onUnLoad方法,在JNI組件被釋放時調用  
  8. void JNI_OnUnload(JavaVM* vm, void* reserved){  
  9.     LOGE("call JNI_OnUnload ~~!!");  
  10. }  

JNI_OnLoad()有兩個重要的做用
指定JNI版本:告訴VM該組件使用那一個JNI版本(若未提供JNI_OnLoad()函數,VM會默認該使用最老的JNI 1.1版),若是要使用新版本的JNI,例如JNI 1.4版,則必須由JNI_OnLoad()函數返回常量JNI_VERSION_1_4(該常量定義在jni.h中) 來告知VM。
初始化設定,當VM執行到System.loadLibrary()函數時,會當即先呼叫JNI_OnLoad()方法,所以在該方法中進行各類資源的初始化操做最爲恰當。
JNI_OnUnload()的做用與JNI_OnLoad()對應,當VM釋放JNI組件時會呼叫它,所以在該方法中進行善後清理,資源釋放的動做最爲合適。
使用registerNativeMethods方法
對Java程序員來講,可能咱們老是會遵循:1.編寫帶有native方法的Java類;—>2.使用javah命令生成.h頭文件;— >3.編寫代碼實現頭文件中的方法,這樣的「官方」 流程,但也許有人沒法忍受那「醜陋」的方法名稱,RegisterNatives方法能幫助你把c/c++中的方法隱射到Java中的native方法, 而無需遵循特定的方法命名格式。來看一段示例代碼吧:

  1. //定義目標類名稱   
  2. static const char *className = "com/okwap/testjni/MyJNI";     
  3.   
  4. //定義方法隱射關係   
  5. static JNINativeMethod methods[] = {   
  6. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},   
  7. };     
  8.   
  9. jint JNI_OnLoad(JavaVM* vm, void* reserved){   
  10. //聲明變量   
  11. jint result = JNI_ERR;   
  12. JNIEnv* env = NULL;   
  13. jclass clazz;   
  14. int methodsLenght;     
  15.   
  16. //獲取JNI環境對象   
  17. if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {   
  18.     LOGE("ERROR: GetEnv failed\n");   
  19.     return JNI_ERR;   
  20. }   
  21. assert(env != NULL);     
  22.   
  23. //註冊本地方法.Load 目標類   
  24. clazz = (*env)->FindClass(env,className);   
  25. if (clazz == NULL) {   
  26. LOGE("Native registration unable to find class '%s'", className);   
  27. return JNI_ERR;   
  28. }     
  29.   
  30. //創建方法隱射關係   
  31. //取得方法長度   
  32. methodsLenght = sizeof(methods) / sizeof(methods[0]);   
  33. if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {   
  34.     LOGE("RegisterNatives failed for '%s'", className);   
  35.     return JNI_ERR;   
  36. }     
  37.   
  38. //   
  39. result = JNI_VERSION_1_4;   
  40. return result;   
  41. }  
  1. //定義目標類名稱  
  2. static const char *className = "com/okwap/testjni/MyJNI";    
  3.   
  4. //定義方法隱射關係  
  5. static JNINativeMethod methods[] = {  
  6. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},  
  7. };    
  8.   
  9. jint JNI_OnLoad(JavaVM* vm, void* reserved){  
  10. //聲明變量  
  11. jint result = JNI_ERR;  
  12. JNIEnv* env = NULL;  
  13. jclass clazz;  
  14. int methodsLenght;    
  15.   
  16. //獲取JNI環境對象  
  17. if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  18.     LOGE("ERROR: GetEnv failed\n");  
  19.     return JNI_ERR;  
  20. }  
  21. assert(env != NULL);    
  22.   
  23. //註冊本地方法.Load 目標類  
  24. clazz = (*env)->FindClass(env,className);  
  25. if (clazz == NULL) {  
  26. LOGE("Native registration unable to find class '%s'", className);  
  27. return JNI_ERR;  
  28. }    
  29.   
  30. //創建方法隱射關係  
  31. //取得方法長度  
  32. methodsLenght = sizeof(methods) / sizeof(methods[0]);  
  33. if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {  
  34.     LOGE("RegisterNatives failed for '%s'", className);  
  35.     return JNI_ERR;  
  36. }    
  37.   
  38. //  
  39. result = JNI_VERSION_1_4;  
  40. return result;  
  41. }  

創建c/c++方法和Java方法之間映射關係的關鍵是 JNINativeMethod 結構,該結構定義在jni.h中,具體定義以下:

  1. typedef struct {   
  2.     const char* name;//java方法名稱   
  3.     const char* signature; //java方法簽名   
  4.     void*       fnPtr;//c/c++的函數指針   
  5. } JNINativeMethod;  
  1. typedef struct {  
  2.     const char* name;//java方法名稱  
  3.     const char* signature; //java方法簽名  
  4.     void*       fnPtr;//c/c++的函數指針  
  5. } JNINativeMethod;  

參照上文示例中初始化該結構的代碼:

  1. //定義方法隱射關係   
  2. static JNINativeMethod methods[] = {   
  3. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},   
  4.  };  
  1. //定義方法隱射關係  
  2. static JNINativeMethod methods[] = {  
  3. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},  
  4.  };  

其中比較難以理解的是第二個參數——signature字段的取值,實際上這些字符與函數的參數類型/返回類型一一對應,其中"()" 中的字符表示參數,後面的則表明返回值。例如"()V" 就表示void func(),"(II)V" 表示 void func(int, int),具體的每個字符的對應關係以下:
字符 Java類型 C/C++類型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
數組則以"["開始,用兩個字符表示:
字符 java類型 c/c++類型
[Z jbooleanArray boolean[]
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
上面的都是基本類型,若是參數是Java類,則以"L"開頭,以";"結尾,中間是用"/"隔開包及類名,而其對應的C函數的參數則爲jobject,一 個例外是String類,它對應C類型jstring,例如:Ljava/lang /String; 、Ljava/net/Socket; 等,若是JAVA函數位於一個嵌入類(也被稱爲內部類),則用$做爲類名間的分隔符,例如:"Landroid/os /FileUtils$FileStatus;"。
使用registerNativeMethods方法不只僅是爲了改變那醜陋的長方法名,最重要的是能夠提升效率,由於當Java類別透過VM呼叫到本地 函數時,一般是依靠VM去動態尋找.so中的本地函數(所以它們才須要特定規則的命名格式),若是某方法須要連續呼叫不少次,則每次都要尋找一遍,因此使 用RegisterNatives將本地函數向VM進行登記,可讓其更有效率的找到函數。
registerNativeMethods方法的另外一個重要用途是,運行時動態調整本地函數與Java函數值之間的映射關係,只須要屢次調用registerNativeMethods()方法,並傳入不一樣的映射表參數便可。
JNI中的日誌輸出
你必定很是熟悉在Java代碼中使用Log.x(TAG,「message」)系列方法,在c/c++代碼中也同樣,不過首先你要include相關頭文件。遺憾的是你使用不一樣的編譯環境( 請參考上文中兩種編譯環境的介紹) ,對應的頭文件略有不一樣。。
若是是在完整源碼編譯環境下,只要include 頭文件,就可使用對應的LOGI、LOGD等方法了,同時請定義LOG_TAG,LOG_NDEBUG等宏值,示例代碼以下:

  1. #define LOG_TAG "HelloJni"  
  2. #define LOG_NDEBUG 0  
  3. #define LOG_NIDEBUG 0  
  4. #define LOG_NDDEBUG 0     
  5.   
  6. #include <STRING.H>   
  7. #include <JNI.H>   
  8. #include <UTILS Log.h>   
  9. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){   
  10.      LOGI("Call stringFromJNI!\n");   
  11.      return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");   
  12. }  
  1. #define LOG_TAG "HelloJni"  
  2. #define LOG_NDEBUG 0  
  3. #define LOG_NIDEBUG 0  
  4. #define LOG_NDDEBUG 0    
  5.   
  6. #include <string.h>  
  7. #include <jni.h>  
  8. #include <utils log="">  
  9. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){  
  10.      LOGI("Call stringFromJNI!\n");  
  11.      return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");  
  12. }  
  13. </utils></jni.h></string.h>  

與日誌相關的.h頭文件,在如下源碼路徑:
myeclair\frameworks\base\include\utils\Log.h
myeclair\system\core\include\cutils\log.h
若是你是在NDK環境下編譯,則須要#include ,示例代碼以下:

  1. #define LOG_TAG "HelloJni"     
  2.   
  3. #include <STRING.H>   
  4. #include <JNI.H>   
  5. #include <UTILS Log.h>   
  6. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){   
  7.     __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"Call stringFromJNI!\n");   
  8.     return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");   
  9. }  
  1. #define LOG_TAG "HelloJni"    
  2.   
  3. #include <string.h>  
  4. #include <jni.h>  
  5. #include <utils log="">  
  6. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){  
  7.     __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"Call stringFromJNI!\n");  
  8.     return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");  
  9. }  
  10. </utils></jni.h></string.h>  

很惋惜,其中用於日誌輸出的方法是: __android_log_print(....) , 並非咱們熟悉的LOG.x(...)系列方法。不過好的一點是android/log.h文件在完整源碼環境下也是可用的,所以,能夠用一下的頭文件來統兩種環境下的差別:

  1. /*  
  2.  * jnilogger.h  
  3.  *  
  4.  *  Created on: 2010-11-15  
  5.  *      Author: INC062805  
  6.  */     
  7.   
  8. #ifndef __JNILOGGER_H_   
  9. #define __JNILOGGER_H_     
  10.   
  11. #include <ANDROID log.h>     
  12.   
  13. #ifdef _cplusplus   
  14. extern "C" {   
  15. #endif     
  16.   
  17. #ifndef LOG_TAG   
  18. #define LOG_TAG    "MY_LOG_TAG"  
  19. #endif     
  20.   
  21. #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)   
  22. #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)   
  23. #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)   
  24. #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)   
  25. #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)     
  26.   
  27. #ifdef __cplusplus   
  28. }   
  29. #endif     
  30.   
  31. #endif /* __JNILOGGER_H_ */  
  1. /* 
  2.  * jnilogger.h 
  3.  * 
  4.  *  Created on: 2010-11-15 
  5.  *      Author: INC062805 
  6.  */    
  7.   
  8. #ifndef __JNILOGGER_H_  
  9. #define __JNILOGGER_H_    
  10.   
  11. #include <android log="">    
  12.   
  13. #ifdef _cplusplus  
  14. extern "C" {  
  15. #endif    
  16.   
  17. #ifndef LOG_TAG  
  18. #define LOG_TAG    "MY_LOG_TAG"  
  19. #endif    
  20.   
  21. #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)  
  22. #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)  
  23. #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)  
  24. #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)  
  25. #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)    
  26.   
  27. #ifdef __cplusplus  
  28. }  
  29. #endif    
  30.   
  31. #endif /* __JNILOGGER_H_ */  
  32. </android>  

你能夠下載以上頭文件,來統一兩種不一樣環境下的使用差別。另外,不要忘了在你的Android.mk文件中加入對類庫的應用,兩種環境下分別是:

  1. ifeq ($(HOST_OS),windows)   
  2. #NDK環境下   
  3.     LOCAL_LDLIBS := -llog   
  4. else  
  5. #完整源碼環境下   
  6.     LOCAL_SHARED_LIBRARIES := libutils   
  7. endif  
  1. ifeq ($(HOST_OS),windows)  
  2. #NDK環境下  
  3.     LOCAL_LDLIBS := -llog  
  4. else  
  5. #完整源碼環境下  
  6.     LOCAL_SHARED_LIBRARIES := libutils  
  7. endif  

Android爲JNI提供的助手方法
myeclair\dalvik\libnativehelper\include\nativehelper
在完整源碼編譯環境下,Android在myeclair\dalvik\libnativehelper\include\nativehelper \JNIHelp.h頭文件中 提供了助手函數 ,用於本地方法註冊、異常處理等任務,還有一個用於計算方法隱射表長度的宏定義:

  1. #ifndef NELEM   
  2. # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))   
  3. #endif     
  4.   
  5.  //有了以上宏定義後,註冊方法能夠按以下寫,該宏定義能夠直接copy到NDK環境下使用:   
  6.  (*env)->RegisterNatives(env,clazz, methods,NELEM(methods));  
    1. #ifndef NELEM  
    2. # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))  
    3. #endif    
    4.   
    5.  //有了以上宏定義後,註冊方法能夠按以下寫,該宏定義能夠直接copy到NDK環境下使用:  
    6.  (*env)->RegisterNatives(env,clazz, methods,NELEM(methods)); 
相關文章
相關標籤/搜索