在上一篇文章《Android JNI開發入門之一》中,我介紹了Android應用程式(APK)怎樣通過JNI調用Native C實現的共用庫。本文將進一步介紹Android應用程式通過JNI調用Native C++實現的共用庫,並實現一個和上文《Android JNI開發入門之一》相同功能的Helloworld應用程式。
兩套不同的API
前文已經提到,Android系統的Java虛擬機器為C和C++實現兩套不同的API,所以我們調用的時候需要注意這一點兒。另外Google並沒有提供
JNI的文檔,我們調用的時候可以參考Android的jni.h檔案,裡面有C和C++的JNI函數原型。也可以把本例的相同功能HelloWorld
庫和上文《Android JNI開發入門之一》進行比較。
C++實現HelloWorld共用庫
在本例中Android應用程式不需要有任何變化,我們需要重新用C++實現HelloWorld共用庫。建立com_simon_Helloworld.cpp檔案,並在檔案中輸入如下內容:
#include <jni.h>#define LOG_TAG "HelloWorld"#include <utils/Log.h> /* * Class: com_simon_Helloworld * Method: print * Signature: ()V *//*JNIEXPORT void JNICALL Java_com_simon_Helloworld_print(JNIEnv *, jobject)*/JNIEXPORT jstring JNICALL Java_com_simon_HelloWorld_printJNI(JNIEnv *env, jobject obj){ LOGI("Hello World From libhelloworld.so!");return env->NewStringUTF("Hello World!");}static const char *classPathName = "com/simon/HelloWorld";static JNINativeMethod methods[] = { {"printJNI", "()Ljava/lang/String;", (void*)Java_com_simon_HelloWorld_printJNI },};/* * Register several native methods for one class. */static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods){ jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'", className); return JNI_FALSE; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { LOGE("RegisterNatives failed for '%s'", className); return JNI_FALSE; } return JNI_TRUE;}/* * Register native methods for all classes we know about. * * returns JNI_TRUE on success. */static int registerNatives(JNIEnv* env){ if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) { return JNI_FALSE; } return JNI_TRUE;}typedef union { JNIEnv* env; void* venv;} UnionJNIEnvToVoid;/* This function will be call when the library first be loaded */jint JNI_OnLoad(JavaVM* vm, void* reserved){ UnionJNIEnvToVoid uenv; JNIEnv* env = NULL; LOGI("JNI_OnLoad!"); if (vm->GetEnv((void**)&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed"); return -1; } env = uenv.env;; if (registerNatives(env) != JNI_TRUE) { LOGE("ERROR: registerNatives failed"); return -1; } return JNI_VERSION_1_4;}
本例與上文《Android JNI開發入門之一》對比有如下幾點不同需要注意:
1、C和C++實現共用庫調用不同JNI API。前面已經提到Android系統JNI為C和C++提供了兩套不同的API。請仔細對比NewStringUTF,GetEnv函數,就會發現JNI API不同。
2、C++版的helloworld共用庫提供了函數映射表。前文《
Android JNI開發入門之一》也已經提到,JNI API為了避免醜陋的函數名,提供了方法向Java虛擬機器註冊函數映射表。這樣當Java調用Native介面的時候,Java虛擬機器就可以不用根據函數名來決定調用哪個函數了,直接通過查詢表格就可以找到需要調用的函數了。
3、我們注意到RegisterNatives第一個參數(C語言介面中是第二個參數)為調用該函數的Java類。這也和標準JNI函數名包含類名(包名和類名)的作用一樣——聲明那個Java類可以調用這個方法。
4、函數映射表的定義非常的怪異。你可以參考
Android JNI 使用的資料結構JNINativeMethod詳解和JNI標準手冊相互關聯類型的部分。
通過對比你會發現C++的實現同樣功能的共用庫比C加入更多的代碼,另外你可能會有疑問既然Java虛擬機器能用通過函數名訪問到相應的Native
code函數,為什麼還要提供註冊映射函數表呢?沒錯!作為一個HelloWorld程式,確實簡單為第一要務!如果Java虛擬機器能用函數名能訪問到相
應的函數的話,我是不會多此一舉來註冊映射函數表。在實踐中我發現:
標準JNI不能通過標準函數名找到C++實現的Helloworld共用庫中的函數,但是C實現的helloworld共用沒有這個問題。我不知道為什麼會這樣,請達人指教。沒有辦法才提供註冊映射函數表。
下面提供一個helloworld共用庫的Makefile——Android.mk:
LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_SRC_FILES:=com_simon_Helloworld.cppLOCAL_C_INCLUDES := $(JNI_H_INCLUDE)LOCAL_MODULE := libhelloworldLOCAL_SHARED_LIBRARIES := libutilsLOCAL_PRELINK_MODULE := falseinclude $(BUILD_SHARED_LIBRARY)
和前文《Android JNI開發入門之一》的Android.mk檔案相比也就是修改了一下源檔案,沒有什麼可說。編譯產生libhelloworld.so檔案,允許前文HelloWorld Android應用程式,你將會得到和前文相同的結果。
JNI的進一步學習
通過上面的例子我們已經初步掌握了Android編寫JNI程式的方法。這也只能算是Android JNI入門而已,有很多JNI相關的內容我們在例子中並沒有涉及到,比如:
1、我們並沒有提到怎樣在Native代碼中回調Java的函數。
2、每個Native的函數中前兩個參數是什麼意思?
如果想用JNI進行Android應用開發我們需要更深入的學習。為了大家共同進步,我這裡也可以提供一些相關的資料和方法。
如果我們想深入學習JNI首先要先熟悉標準的JNI。推薦大家學習《Java Native Interface: Programmer’s Guide and Specification》,權威的標準JNI學習文檔。
前文提到Google沒有Android JNI編程提供文檔,但是在網上Simon也找到了一篇非常好的文檔供大家參考——JNI Examples for Android。這篇文章中舉了一個例子包含了Android JNI開發的方方面面,想深入學習Android JNI開發的朋友請仔細研讀。
總結
俗話說得好“問道有先後,如是而已”,Simon也是剛剛才開始學習Android JNI編程,對JNI的理解上面難免有一些地方有錯誤,歡迎朋友們指正。
參考資料:
Android JNI 使用的資料結構JNINativeMethod詳解
Android JNI執行個體
JNI Examples for Android
How to add a new module to Android
Android JNI(實現自己的JNI_OnLoad函數)
Android中JNI編程的那些事兒
Java Native Interface: Programmer’s Guide and Specification
Java Native Interface Specification
原文