前文已經(jīng)提到,Android系統的Java虛擬機為C和C++實(shí)現兩套不同的API,所以我們調用的時(shí)候需要注意這一點(diǎn)兒。另外Google并沒(méi)有提供JNI的文檔,我們調用的時(shí)候可以參考Android的jni.h文件,里面有C和C++的JNI函數原型。也可以把本例的相同功能HelloWorld庫和上文《Android JNI開(kāi)發(fā)入門(mén)之一》進(jìn)行比較。
在本例中Android應用程序不需要有任何變化,我們需要重新用C++實(shí)現HelloWorld共享庫。創(chuàng )建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開(kāi)發(fā)入門(mén)之一》對比有如下幾點(diǎn)不同需要注意:
1、C和C++實(shí)現共享庫調用不同JNI API。前面已經(jīng)提到Android系統JNI為C和C++提供了兩套不同的API。請仔細對比NewStringUTF,GetEnv函數,就會(huì )發(fā)現JNI API不同。
2、C++版的helloworld共享庫提供了函數映射表。前文《
Android JNI開(kāi)發(fā)入門(mén)之一》也已經(jīng)提到,JNI API為了避免丑陋的函數名,提供了方法向Java虛擬機注冊函數映射表。這樣當Java調用Native接口的時(shí)候,Java虛擬機就可以不用根據函數名來(lái)決定調用哪個(gè)函數了,直接通過(guò)查詢(xún)表格就可以找到需要調用的函數了。
3、我們注意到RegisterNatives第一個(gè)參數(C語(yǔ)言接口中是第二個(gè)參數)為調用該函數的Java類(lèi)。這也和標準JNI函數名包含類(lèi)名(包名和類(lèi)名)的作用一樣——聲明那個(gè)Java類(lèi)可以調用這個(gè)方法。
4、函數映射表的定義非常的怪異。你可以參考
Android JNI 使用的數據結構JNINativeMethod詳解和JNI標準手冊相關(guān)類(lèi)型的部分。
通過(guò)對比你會(huì )發(fā)現C++的實(shí)現同樣功能的共享庫比C加入更多的代碼,另外你可能會(huì )有疑問(wèn)既然Java虛擬機能用通過(guò)函數名訪(fǎng)問(wèn)到相應的Native code函數,為什么還要提供注冊映射函數表呢?沒(méi)錯!作為一個(gè)HelloWorld程序,確實(shí)簡(jiǎn)單為第一要務(wù)!如果Java虛擬機能用函數名能訪(fǎng)問(wèn)到相應的函數的話(huà),我是不會(huì )多此一舉來(lái)注冊映射函數表。在實(shí)踐中我發(fā)現:
標準JNI不能通過(guò)標準函數名找到C++實(shí)現的Helloworld共享庫中的函數,但是C實(shí)現的helloworld共享沒(méi)有這個(gè)問(wèn)題。我不知道為什么會(huì )這樣,請達人指教。沒(méi)有辦法才提供注冊映射函數表。
下面提供一個(gè)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開(kāi)發(fā)入門(mén)之一》的Android.mk文件相比也就是修改了一下源文件,沒(méi)有什么可說(shuō)。編譯生成libhelloworld.so文件,允許前文HelloWorld Android應用程序,你將會(huì )得到和前文相同的結果。
通過(guò)上面的例子我們已經(jīng)初步掌握了Android編寫(xiě)JNI程序的方法。這也只能算是Android JNI入門(mén)而已,有很多JNI相關(guān)的內容我們在例子中并沒(méi)有涉及到,比如:
1、我們并沒(méi)有提到怎樣在Native代碼中回調Java的函數。
2、每個(gè)Native的函數中前兩個(gè)參數是什么意思?
如果想用JNI進(jìn)行Android應用開(kāi)發(fā)我們需要更深入的學(xué)習。為了大家共同進(jìn)步,我這里也可以提供一些相關(guān)的資料和方法。
如果我們想深入學(xué)習JNI首先要先熟悉標準的JNI。推薦大家學(xué)習《Java Native Interface: Programmer’s Guide and Specification》,權威的標準JNI學(xué)習文檔。
前文提到Google沒(méi)有Android JNI編程提供文檔,但是在網(wǎng)上Simon也找到了一篇非常好的文檔供大家參考——JNI Examples for Android。這篇文章中舉了一個(gè)例子包含了Android JNI開(kāi)發(fā)的方方面面,想深入學(xué)習Android JNI開(kāi)發(fā)的朋友請仔細研讀。
俗話(huà)說(shuō)得好“問(wèn)道有先后,如是而已”,Simon也是剛剛才開(kāi)始學(xué)習Android JNI編程,對JNI的理解上面難免有一些地方有錯誤,歡迎朋友們指正。
參考資料:
Android JNI 使用的數據結構JNINativeMethod詳解
How to add a new module to Android
Android JNI(實(shí)現自己的JNI_OnLoad函數)
聯(lián)系客服