/********************************************************************************************
* author:conowen@大鐘
* E-mail:conowen@hotmail.com
* http://blog.csdn.net/conowen
* 註:本文為原創,僅作為學習交流使用,轉載請標明作者及出處。
********************************************************************************************/
1、JNI簡介
JNI全稱為Java Native Interface(JAVA本地調用)。從Java1.1開始,JNI成為java平台的一部分,它允許Java代碼和其他語言寫的代碼(如C&C++)進行互動。並非從Android發布才引入JNI的概念的。
2、JNI與NDK
簡單來說,Android的NDK提供了一些交叉編譯工具鏈和Android內建的庫,這些Android的庫可以讓開發人員在編寫本地語言的程式時調用。而NDK提供的交叉編譯工具鏈就對已經編寫好的C&C++代碼進行編譯,產生庫。
當然了,你也可以自己搭建交叉編譯環境,而不用NDK的工具和庫。然後產生庫,只要規範操作,一樣可以產生能讓JAVA層成功調用的庫檔案的。
利用NDK進行編譯本地語言可以參考這篇博文:http://blog.csdn.net/conowen/article/details/7522667
3、JNI 調用流程
眾所周知,Android的應用程式層的類都是以Java寫的,這些Java類編譯為Dex檔案之後,必須靠Dalvik虛擬機器( Virtual Machine)來執行。假如在執行java程式時,需要載入C&C++函數時,Dalvik虛擬機器就會去載入C&C++的庫,(System.loadLibrary("libName");)讓java層能順利地調用這些本地函數。需要清楚一點,這些C&C++的函數並不是在Dalvik虛擬機器中啟動並執行,所以效率和速度要比在Dalvik虛擬機器中運行得快很多。
Dalvik虛擬機器成功載入庫之後,就會自動地尋找庫裡面的JNI_OnLoad函數,這個函數用途如下:
(1)告訴Dalvik虛擬機器此C庫使用哪一個JNI版本。如果你的庫裡面沒有寫明JNI_OnLoad()函數,VM會預設該庫使用最老的JNI 1.1版本。但是新版的JNI做了很多的擴充,也最佳化了一些內容,如果需要使用JNI的新版功能,就必須在JNI_OnLoad()函式宣告JNI的版本。如
result = JNI_VERSION_1_4;
當沒有JNI_OnLoad()函數時,Android調試資訊會做出如下提示(No JNI_OnLoad found)
04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea9804-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea9804-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init
(2)因為Dalvik虛擬機器載入C庫時,第一件事是調用JNI_OnLoad()函數,所以我們可以在JNI_OnLoad()裡面進行一些初始化工作,如註冊JNI函數等等。註冊本地函數,可以加快java層調用本地函數的效率。
另外:與JNI_OnLoad()函數相對應的有JNI_OnUnload()函數,當虛擬機器釋放該C庫時,則會調用JNI_OnUnload()函數來進行善後清除動作。
4、例子(關於jni裡面的資料類型轉換與常用jni方法下一篇博文介紹)
下面以havlenapetr的FFmpeg工程裡面的onLoad.cpp為例詳細說一下:
//onLoad.cpp檔案#define TAG "ffmpeg_onLoad"#include <stdlib.h>#include <android/log.h>#include "jniUtils.h"extern "C" {extern int register_android_media_FFMpegAVRational(JNIEnv *env);#ifdef BUILD_WITH_CONVERTORextern int register_android_media_FFMpeg(JNIEnv *env);#endifextern int register_android_media_FFMpegAVFormatContext(JNIEnv *env);extern int register_android_media_FFMpegAVInputFormat(JNIEnv *env);}extern int register_android_media_FFMpegAVCodecContext(JNIEnv *env);extern int register_android_media_FFMpegUtils(JNIEnv *env);extern int register_android_media_FFMpegAVFrame(JNIEnv *env);#ifdef BUILD_WITH_PLAYERextern int register_android_media_FFMpegPlayerAndroid(JNIEnv *env);#endifstatic JavaVM *sVm;/* * Throw an exception with the specified class and an optional message. */int jniThrowException(JNIEnv* env, const char* className, const char* msg) { jclass exceptionClass = env->FindClass(className); if (exceptionClass == NULL) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Unable to find exception class %s", className); return -1; } if (env->ThrowNew(exceptionClass, msg) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed throwing '%s' '%s'", className, msg); } return 0;}JNIEnv* getJNIEnv() { JNIEnv* env = NULL; if (sVm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR,TAG,"Failed to obtain JNIEnv"); return NULL; } return env;}/* * Register native JNI-callable methods. * * "className" looks like "java/lang/String". */int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)/*從com_media_ffmpeg_FFMpegPlayer.cpp檔案跳到此,完成最後的註冊 * 向 Dalvik虛擬機器(即AndroidRuntime)登記傳過來的參數gMethods[]所含的本地函數 */{ jclass clazz; __android_log_print(ANDROID_LOG_INFO, TAG, "Registering %s natives\n", className); clazz = env->FindClass(className); if (clazz == NULL) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Native registration unable to find class '%s'\n", className); return -1; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { __android_log_print(ANDROID_LOG_ERROR, TAG, "RegisterNatives failed for '%s'\n", className); return -1; } return 0;}//Dalvik虛擬機器載入C庫時,第一件事是調用JNI_OnLoad()函數jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL;//定義JNI Env jint result = JNI_ERR;sVm = vm;/*JavaVM::GetEnv 原型為 jint (*GetEnv)(JavaVM*, void**, jint); * GetEnv()函數返回的 Jni 環境對每個線程來說是不同的, * 由於Dalvik虛擬機器通常是Multi-threading的。每一個線程調用JNI_OnLoad()時, * 所用的JNI Env是不同的,因此我們必須在每次進入函數時都要通過vm->GetEnv重新擷取 * *///得到JNI Env if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "GetEnv failed!"); return result; } __android_log_print(ANDROID_LOG_INFO, TAG, "loading . . .");/*開始註冊 * 傳入參數是JNI env * 由於下面有很多class,只以register_android_media_FFMpegPlayerAndroid(env)為例子 */ #ifdef BUILD_WITH_CONVERTOR if(register_android_media_FFMpeg(env) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpeg"); goto end; }#endif if(register_android_media_FFMpegAVFormatContext(env) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVFormatContext"); goto end;} if(register_android_media_FFMpegAVCodecContext(env) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVCodecContext"); goto end;} if(register_android_media_FFMpegAVRational(env) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVRational"); goto end; }if(register_android_media_FFMpegAVInputFormat(env) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVInputFormat"); goto end; }if(register_android_media_FFMpegUtils(env) != JNI_OK) {__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegUtils");goto end;}if(register_android_media_FFMpegAVFrame(env) != JNI_OK) {__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVFrame");goto end;}#ifdef BUILD_WITH_PLAYER if(register_android_media_FFMpegPlayerAndroid(env) != JNI_OK) {//跳到----》com_media_ffmpeg_FFMpegPlayer.cpp檔案 __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegPlayerAndroid"); goto end; }#endif __android_log_print(ANDROID_LOG_INFO, TAG, "loaded"); result = JNI_VERSION_1_4;end: return result;}
//com_media_ffmpeg_FFMpegPlayer.cpp檔案/* * * 由於代碼量較大,com_media_ffmpeg_FFMpegPlayer.cpp開始的一部分省略,只是貼出註冊函數的相關部分。 */static const char* const kClassPathName = "com/media/ffmpeg/FFMpegPlayer";/* * 由於gMethods[]是一個<名稱,函數指標>對照表,在程式執行時, * 可多次調用registerNativeMethods()函數來更換本地函數的指標, * 從而達到彈性調用本地函數的目的。 */static JNINativeMethod gMethods[] = { {"setDataSource", "(Ljava/lang/String;)V", (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource}, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface}, {"prepare", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_prepare}, {"_start", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_start}, {"_stop", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_stop}, {"getVideoWidth", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight}, {"seekTo", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_seekTo}, {"_pause", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_pause}, {"isPlaying", "()Z", (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying}, {"getCurrentPosition", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition}, {"getDuration", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getDuration}, {"_release", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_release}, {"_reset", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_reset}, {"setAudioStreamType", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType}, {"native_init", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_native_init}, {"native_setup", "(Ljava/lang/Object;)V", (void *)com_media_ffmpeg_FFMpegPlayer_native_setup}, {"native_finalize", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize}, {"native_suspend_resume", "(Z)I", (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume},};int register_android_media_FFMpegPlayerAndroid(JNIEnv *env) {return jniRegisterNativeMethods(env, kClassPathName, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));/*跳到OnLoad.cpp檔案中的 * jint jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) */}