框架基础JNI
轉(zhuǎn)載請(qǐng)標(biāo)明出處:?
http://blog.csdn.net/yujun411522/article/details/46342793
本文出自:【yujun411522的博客】
2.1 概述
JNI(Java Native Interface)。這是一個(gè)既熟悉又陌生的名詞。熟悉是由于java之中JNI技術(shù)很常見(jiàn);陌生是由于絕大多數(shù)時(shí)候我們并沒(méi)有關(guān)心這個(gè)技術(shù)在java中是怎樣使用的。 先看一下在J2SE中的File類中一個(gè)方法:setLastModified方法 public boolean setLastModified(long time) {if (time < 0) {throw new IllegalArgumentException("time < 0");}return setLastModifiedImpl( path, time);//調(diào)用本地方法setLastModifiedImpl方法}private static native boolean setLastModifiedImpl(String path, long time); 我們能夠看出setLastModifiedImpl的聲明方式非常像抽象函數(shù),僅僅有函數(shù)名稱,沒(méi)有函數(shù)實(shí)現(xiàn)。在J2SE中,這樣的native方法非常多。這里為什么要使用這樣的技術(shù)呢? 我們知道java語(yǔ)言不能操作訪問(wèn)硬件,硬件的訪問(wèn)是依靠Native語(yǔ)言(通常是C/C++語(yǔ)言)。可是不同的平臺(tái)有不同的時(shí)間機(jī)制:比方說(shuō)打開(kāi)一個(gè)文件。在windows中使用openFile函數(shù),在Linux中則是使用open函數(shù)。假設(shè)在編寫(xiě)java語(yǔ)言時(shí)還要考慮跨平臺(tái)。那顯然不符合java"一次編寫(xiě),處處執(zhí)行"的思想。那應(yīng)該怎么辦呢? java是能夠跨平臺(tái)的,java跨平臺(tái)的基礎(chǔ)就是JVM在不同的詳細(xì)平臺(tái)上的不同實(shí)現(xiàn),JVM是不跨平臺(tái)的,也就是說(shuō)JVM本身無(wú)法做到與平臺(tái)無(wú)關(guān),必須在不同的平臺(tái)之上有不同的實(shí)現(xiàn)機(jī)制。那么JNI的目的就是對(duì)java層屏蔽不同平臺(tái)之間的差異。java中打開(kāi)一個(gè)文件。僅僅須要聲明它是一個(gè)native方法,不須要關(guān)心是執(zhí)行在哪個(gè)平臺(tái)之上的,這個(gè)工作由虛擬機(jī)來(lái)選擇。
不同平臺(tái)的虛擬機(jī)有自己的實(shí)現(xiàn)方式。而java層不須要關(guān)心平臺(tái)的差異。
這僅僅是JNI的一個(gè)功能:在java中調(diào)用native語(yǔ)言;還有一個(gè)功能就是native語(yǔ)言能夠訪問(wèn)java層,能夠看出JNI的作用就是連接java層和native層:也就是java和native通過(guò)JNI的方式連接起來(lái)。 以下我們來(lái)先兩個(gè)android中JNI使用的樣例。
2.2 android中JNI實(shí)例分析 以下介紹兩個(gè)樣例:一個(gè)是開(kāi)發(fā)過(guò)程中很常見(jiàn)。用來(lái)打印日志信息的android.util.Log類;一個(gè)是android系統(tǒng)用來(lái)掃描多媒體文件的MediaScanner類
2.2.1Log類jni實(shí)例分析 這個(gè)類在開(kāi)發(fā)過(guò)程中經(jīng)經(jīng)常使用它來(lái)打印日志信息: Log.d("tag","Msg"); 我們先看一個(gè)Log類在java層的實(shí)現(xiàn): public final class Log{public static int d(String tag, String msg) {return println_native( LOG_ID_MAIN, DEBUG , tag, msg);//調(diào)用本地方法println_native}public static native int println_native(int bufID, int priority, String tag, String msg); } java層聲明了native方法:println_native,它們?cè)贘NI層中是怎樣實(shí)現(xiàn)?
看它相應(yīng)的jni代碼就可以。
一個(gè)非常實(shí)際的問(wèn)題是:我們?nèi)ツ睦镎宜麄兊腏NI層實(shí)現(xiàn),這里先給出答案,后面會(huì)討論這個(gè)問(wèn)題 Log類的JNI文件是frameworks\base\core\jni\android_util_Log.cpp。當(dāng)中println_native方法: android_util_Log.cpp中println_native函數(shù)的實(shí)現(xiàn): /* * In class android.util.Log: * public static native int println_native(int buffer, int priority, String tag, String msg) */ static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,jint bufID, jint priority, jstring tagObj, jstring msgObj) {//比java層的println_native 函數(shù)多了兩個(gè)參數(shù)JNIEnv* env, jobject clazz,其它的參數(shù)和java層一一相應(yīng)。const char* tag = NULL;const char* msg = NULL;if (msgObj == NULL) {//異常處理,后面會(huì)涉及到j(luò)niThrowNullPointerException(env, "println needs a message");return -1;}if (bufID < 0 || bufID >= LOG_ID_MAX) {jniThrowNullPointerException(env, "bad bufID");return -1;}if (tagObj != NULL)// 將java 中String對(duì)象轉(zhuǎn)換成本地UTF-8字符串tag = env->GetStringUTFChars(tagObj, NULL);msg = env->GetStringUTFChars(msgObj, NULL);//繼續(xù)調(diào)用本地方法 __android_log_buf_write。int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);if (tag != NULL)//使用完之后要釋放資源。否則導(dǎo)致JVM內(nèi)存泄露env->ReleaseStringUTFChars(tagObj, tag);env->ReleaseStringUTFChars(msgObj, msg);return res; } 這里實(shí)際是調(diào)用本地方法__android_log_buf_write函數(shù)進(jìn)行打印(興許更新__android_log_buf_write),如今僅僅須要知道java層的println_native方法的jni層實(shí)現(xiàn)就是android_util_Log_println_native函數(shù)就可以。回到剛才那個(gè)問(wèn)題:java層的聲明的println_native方法和native層的android_util_Log_println_native是怎樣關(guān)聯(lián)在一起的呢? 我們發(fā)如今frameworks\base\core\jni\android_util_Log.cpp文件里有一個(gè)register_android_util_Log方法,推測(cè)可能和兩者關(guān)聯(lián)有關(guān): int register_android_util_Log(JNIEnv* env) {jclass clazz = env->FindClass("android/util/Log");if (clazz == NULL) {LOGE("Can't find android/util/Log");return -1;}//通過(guò)jni操作java相應(yīng)。后面會(huì)介紹。levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));//調(diào)用了registerNativeMethod方法return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)); } 當(dāng)中g(shù)Methods是一個(gè)數(shù)組: /* * JNI registration. */ static JNINativeMethod gMethods[] = {/* name, signature, funcPtr */{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }, }; 當(dāng)中JNINativeMethod?是一個(gè)結(jié)構(gòu)體類型。在jni.h文件之中 typedef struct {const char* name; //java層的native函數(shù)的名稱const char* signature; //該native函數(shù)的簽名(返回值+參數(shù)列表)。由于java中支持函數(shù)重載,所以函數(shù)名稱+函數(shù)簽名才干明白確定一個(gè)函數(shù)void* fnPtr; //函數(shù)指針,指向jni層該相應(yīng)的函數(shù)實(shí)現(xiàn) } JNINativeMethod; println_native函數(shù)出如今gMethods數(shù)組之中 { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native } "println_native", //相應(yīng)java層的函數(shù)名稱為println_native的函數(shù) "(IILjava/lang/String;Ljava/lang/String;)I",//該函數(shù)簽名為(IILjava/lang/String;Ljava/lang/String;)I,關(guān)于函數(shù)簽名后面會(huì)介紹含義(void*) android_util_Log_println_native//該函數(shù)在jni層實(shí)現(xiàn)的方法指針為 (void*) android_util_Log_println_native
了解gMethods之后。register_android_util_Log中調(diào)用了registerNativeMethods方法,該方法在frameworks\base\core\jni\AndroidRuntime.cpp /* * Register native methods using JNI. */ /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,const char* className, const JNINativeMethod* gMethods, int numMethods) {return jniRegisterNativeMethods(env, className, gMethods, numMethods); } 接著調(diào)用了?jniRegisterNativeMethods方法,該方法在: dalvik\libnativehelper.JNIHelp.cpp文件里: extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods) {JNIEnv* e = reinterpret_cast<JNIEnv*>(env);LOGV("Registering %s natives", className);scoped_local_ref<jclass> c(env, findClass(env, className));if (c.get() == NULL) {LOGE("Native registration unable to find class '%s', aborting", className);abort();}if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {LOGE("RegisterNatives failed for '%s', aborting", className);abort();}return 0; } 終于調(diào)用RegisterNatives(JNIEnv *env,jclass clazz,const JNINativeMethod *method,jint nMethods)方法,該方法向clazz類注冊(cè)在method數(shù)組中本地方法方法。這樣虛擬機(jī)就能夠建立起java層和jni層的兩個(gè)函數(shù)之間的相應(yīng)關(guān)系。
如今我們已經(jīng)知道register_android_util_Log方法能夠完畢println_native在java層和jni層的映射,可是在那么調(diào)用了register_android_util_Log方法呢? 在AndroidRuntime.cpp中的register_jni_procs方法。該方法會(huì)調(diào)用register_android_util_Log方法。關(guān)于這部分后面會(huì)介紹
2.2.1 MediaScanner 看完Log,我們?cè)倏匆粋€(gè)樣例:MediaScanner java層相應(yīng)的是MediaScanner,里面定義了一些函數(shù)須要native層來(lái)實(shí)現(xiàn) 先看java層的MediaScanner: public class MediaScanner{ static {System.loadLibrary("media_jni");native_init();} ...private static native final void native_init();private native void processFile(String path, String mimeType, MediaScannerClient client); } MediScanner在jni層相應(yīng)的是frameworks\base\media\jni\android_media_MediaScanner.cpp java層中的native_init在jni層的實(shí)現(xiàn) // This function gets a field ID, which in turn causes class initialization. // It is called from a static block in MediaScanner, which won't run until the // first time an instance of this class is used. //java層中native_init 在jni層的實(shí)現(xiàn) static void android_media_MediaScanner_native_init(JNIEnv *env) {LOGV("native_init");// kClassMediaScannerClient = "android/media/MediaScannerClient";jclass clazz = env->FindClass(kClassMediaScanner);if (clazz == NULL) {return;}//在fields.context 中保存int類型的mNativeContext成員變量的fieldIdfields.context = env->GetFieldID(clazz, "mNativeContext", "I");if (fields.context == NULL) {return;} } java層中processFile在jni層的實(shí)現(xiàn) static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,jstring mimeType, jobject client) {LOGV("processFile");// Lock already hold by processDirectoryMediaScanner *mp = getNativeScanner_l(env, thiz);if (mp == NULL) {//異常處理。后面會(huì)介紹異常處理 jniThrowException(env, kRunTimeException, "No scanner available");return;}if (path == NULL) {//異常處理 jniThrowException(env, kIllegalArgumentException, NULL);return;}const char *pathStr = env->GetStringUTFChars(path, NULL);if (pathStr == NULL) { // Out of memoryreturn;}const char *mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);if (mimeType && mimeTypeStr == NULL) { // Out of memory// ReleaseStringUTFChars can be called with an exception pending.env->ReleaseStringUTFChars(path, pathStr);return;}MyMediaScannerClient myClient(env, client);//調(diào)用MediaScanner的本地方法processFileMediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);if (result == MEDIA_SCAN_RESULT_ERROR) {LOGE("An error occurred while scanning file '%s'.", pathStr);}env->ReleaseStringUTFChars(path, pathStr);if (mimeType) {env->ReleaseStringUTFChars(mimeType, mimeTypeStr);}
在上一個(gè)樣例中我們找到register_android_util_Log能夠用來(lái)完畢函數(shù)println_native在jni層的映射,相同在android_media_MediaScanner.cpp也找到了類似的函數(shù): // This function only registers the native methods, and is called from // JNI_OnLoad in android_media_MediaPlayer.cpp int register_android_media_MediaScanner(JNIEnv *env) {return AndroidRuntime::registerNativeMethods(env,kClassMediaScanner, gMethods, NELEM(gMethods)); } 當(dāng)中g(shù)Methods: static JNINativeMethod gMethods[] = {//..{"processFile", //java層名稱為processFile "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", //java層函數(shù)簽名為(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;) (void *)android_media_MediaScanner_processFile //該函數(shù)在jni層中的函數(shù)指針為(void *)android_media_MediaScanner_processFile },//..{"native_init", //java層名稱為native_init "()V", //java層函數(shù)簽名為()V (void *)android_media_MediaScanner_native_init 該函數(shù)在jni層中的函數(shù)指針為(void *)android_media_MediaScanner_native_init },}; 后面的操作就和Log中的流程一樣,不再贅述。
2.2 JNI注冊(cè)方式 上面的兩個(gè)樣例中都涉及到一個(gè)問(wèn)題。怎樣將java層和jni層相應(yīng)的函數(shù)一一關(guān)聯(lián)起來(lái),這就是jni注冊(cè)。有了這個(gè)注冊(cè),在java層調(diào)用native方法時(shí)。就能非常方便找到j(luò)ni層的實(shí)現(xiàn)并運(yùn)行。 注冊(cè)方式有兩種方式:靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)
2.2.1 JNI靜態(tài)注冊(cè) 靜態(tài)注冊(cè)的思想:依據(jù)函數(shù)名稱來(lái)建立java函數(shù)和jni函數(shù)之間的關(guān)聯(lián)關(guān)系 詳細(xì)方法: 1.編寫(xiě)java文件,編譯生成class文件 2.使用javah工具,javah -o output packagename.classname 命令生成output.h的jni頭文件。
這樣的方式要求函數(shù)的命名符合一定的要求,主要有以下幾個(gè)部分拼接而成 1.Java_前綴 2.全路徑類名稱(將.替換為/) 3下劃線_ 4參數(shù)列表加入JNIEnv* env,jobject class 5java層函數(shù)的參數(shù)映射 6返回值
採(cǎi)用這樣的方法,在java層調(diào)用native方法時(shí),會(huì)在jni庫(kù)中尋找按上述規(guī)則生成的jni函數(shù)。假設(shè)找到就將兩個(gè)之間建立起一個(gè)關(guān)系。也就是保存在jni層這個(gè)函數(shù)指針。
下次調(diào)用時(shí)直接使用這個(gè)函數(shù)指針就可以。
這么做會(huì)非常繁瑣,所以出現(xiàn)了第二中方式:jni動(dòng)態(tài)注冊(cè)2.2.2 JNI動(dòng)態(tài)注冊(cè) 上面提到了能夠保存java層函數(shù)在jni層的函數(shù)指針。那么我們直接保存這樣的關(guān)系就能夠了。
這就涉及到2.1中的一個(gè)結(jié)構(gòu)體類型JNINativeMethod
typedef struct {const char* name; //java層的native函數(shù)的名稱const char* signature; //該native函數(shù)的簽名(返回值+參數(shù)列表)。由于java中支持函數(shù)重載,所以函數(shù)名稱+函數(shù)簽名才干明白確定一個(gè)函數(shù)void* fnPtr; //函數(shù)指針,指向jni層該相應(yīng)的函數(shù)實(shí)現(xiàn) } JNINativeMethod; 然后的流程就是上述兩個(gè)樣例的流程:register_android_util_Log、register_android_media_MediaScanner函數(shù),調(diào)用AndroidRuntime::registerNativeMethods函數(shù),AndroidRuntime::registerNativeMethods中調(diào)用JNIHelp中jniRegisterNativeMethods方法。而register_android_media_MediaScanner是在哪里調(diào)用的呢? 就是在android_media_MediaPlayer.cpp中的JNI_OnLoad方法中調(diào)用。 jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {LOGE("ERROR: GetEnv failed\n");goto bail;}assert(env != NULL);if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail;}if (register_android_media_MediaRecorder(env) < 0) {LOGE("ERROR: MediaRecorder native registration failed\n");goto bail;}//在這里調(diào)用register_android_media_MediaScanner 注冊(cè)函數(shù)if (register_android_media_MediaScanner(env) < 0) {LOGE("ERROR: MediaScanner native registration failed\n");goto bail;}if (register_android_media_MediaMetadataRetriever(env) < 0) {LOGE("ERROR: MediaMetadataRetriever native registration failed\n");goto bail;}if (register_android_media_AmrInputStream(env) < 0) {LOGE("ERROR: AmrInputStream native registration failed\n");goto bail;}if (register_android_media_ResampleInputStream(env) < 0) {LOGE("ERROR: ResampleInputStream native registration failed\n");goto bail;}if (register_android_media_MediaProfiles(env) < 0) {LOGE("ERROR: MediaProfiles native registration failed");goto bail;}if (register_android_mtp_MtpDatabase(env) < 0) {LOGE("ERROR: MtpDatabase native registration failed");goto bail;}if (register_android_mtp_MtpDevice(env) < 0) {LOGE("ERROR: MtpDevice native registration failed");goto bail;}if (register_android_mtp_MtpServer(env) < 0) {LOGE("ERROR: MtpServer native registration failed");goto bail;}/* success -- return valid version number */result = JNI_VERSION_1_4; } 能夠看出在該方法中還調(diào)用了其它的注冊(cè)函數(shù),所以假設(shè)我們動(dòng)態(tài)注冊(cè),就須要實(shí)現(xiàn)該函數(shù)。
2.3 JNIEnv 上面的代碼中我們都使用了JNIEnv這個(gè)指針,利用它能夠?qū)崿F(xiàn)jni函數(shù)的注冊(cè),它的功能遠(yuǎn)不止這些。它還能夠訪問(wèn)java虛擬機(jī),操作java對(duì)象。是jni中最重要的一個(gè)概念。 JNIEnv是一個(gè)和線程相關(guān)的代表JNI環(huán)境的結(jié)構(gòu)體:
它指向虛擬機(jī)內(nèi)部數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)又能夠指向一個(gè)一個(gè)的jni函數(shù)。能夠通過(guò)它來(lái)調(diào)用jni函數(shù)。 JNIEnv結(jié)構(gòu)體在dalvik\libnativehelper\include\nativehelper.Jni.h定義 struct _JNIEnv; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; //c++中使用_JNIEnv .. #else typedef const struct JNINativeInterface* JNIEnv; //c語(yǔ)言中使用JNINativeInterface .. #endif 先看_JNIEnv(C++語(yǔ)言中使用)? /* * C++ object wrapper. * * This is usually overlaid on a C struct whose first element is a * JNINativeInterface*. We rely somewhat on compiler behavior. */ struct _JNIEnv {/* do not rename this; it does not seem to be entirely opaque */const struct JNINativeInterface* functions;#if defined(__cplusplus) ..非常多方法jclass FindClass(const char* name){ return functions->FindClass(this, name); }jthrowable ExceptionOccurred(){ return functions->ExceptionOccurred(this); }void ExceptionDescribe(){ functions->ExceptionDescribe(this); }void ExceptionClear(){ functions->ExceptionClear(this); }jobject NewGlobalRef(jobject obj){ return functions->NewGlobalRef(this, obj); }void DeleteGlobalRef(jobject globalRef){ functions->DeleteGlobalRef(this, globalRef); }jmethodID GetMethodID(jclass clazz, const char* name, const char* sig){ return functions->GetMethodID(this, clazz, name, sig); }jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig){ return functions->GetStaticMethodID(this, clazz, name, sig); }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); }//各種GetXXXField 方法void SetObjectField(jobject obj, jfieldID fieldID, jobject value){ functions->SetObjectField(this, obj, fieldID, value); }//各種SetXXXField 方法 #define CALL_TYPE_METHOD(_jtype, _jname) _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) { _jtype result; va_list args; va_start(args, methodID); result = functions->Call##_jname##MethodV(this, obj, methodID,args); va_end(args); return result; }#define CALL_STATIC_TYPE_METHOD(_jtype, _jname) _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, ..) { _jtype result; va_list args; va_start(args, methodID); result = functions->CallStatic##_jname##MethodV(this, clazz,methodID, args); va_end(args); return result; }//..其它方法 } 同_JNIEnv,JNINativeInterface(C語(yǔ)言中使用)? struct JNINativeInterface {jclass (*FindClass)(JNIEnv*, const char*);jint (*Throw)(JNIEnv*, jthrowable);jint (*ThrowNew)(JNIEnv *, jclass, const char *);jthrowable (*ExceptionOccurred)(JNIEnv*);void (*ExceptionDescribe)(JNIEnv*);void (*ExceptionClear)(JNIEnv*);jobject (*NewGlobalRef)(JNIEnv*, jobject);void (*DeleteGlobalRef)(JNIEnv*, jobject);jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);//..其它方法 } 上面解釋了:JNIEnv是一個(gè)和線程相關(guān)的概念,不能將一個(gè)線程的JNIEnv從一個(gè)線程傳遞到還有一個(gè)線程中。
同一個(gè)線程對(duì)本地方法的多次調(diào)用,使用都是同一個(gè)JNIEnv。
在JNI_OnLoad(JavaVM* vm, void* reserved)中第一個(gè)參數(shù)類型為JavaVM。它是虛擬機(jī)在jni層的代表。JavaVM和JNIEnv的關(guān)系是: 在調(diào)用AttachCurrentThread時(shí)。就返回該線程的JNIEnv jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) { return functions->AttachCurrentThread(this, p_env, thr_args); } 在調(diào)用DetachCurrentThread時(shí),就釋放該線程的JNIEnv 資源 jint DetachCurrentThread() { return functions->DetachCurrentThread(this); }
2.4 java中調(diào)用jni的實(shí)現(xiàn)方法 主要有數(shù)據(jù)類型轉(zhuǎn)換,jni函數(shù)簽名 2.4.1?數(shù)據(jù)類型轉(zhuǎn)換 分為兩個(gè)部分:基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。 先看基本數(shù)據(jù)類型轉(zhuǎn)換 1.基本數(shù)據(jù)類型轉(zhuǎn)換 /* * Primitive types that match up with Java equivalents. */ #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 相應(yīng)關(guān)系就是前面加一個(gè)字母j
注意字長(zhǎng)變化,java中char是8位,而jchar則是16位。
2.引用類型變化 jni中引用類型有jobject、jclass、jarray、jstring、jthrowable以及九種數(shù)組類型。繼承結(jié)構(gòu):
和java引用相應(yīng)的關(guān)系是:
比方函數(shù)static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,?jint bufID, jint priority, jstring tagObj, jstring msgObj) java中int相應(yīng)jni中的jint,java中的String相應(yīng)jni中的jstring 函數(shù)android_media_MediaScanner_processFile(?JNIEnv *env, jobject thiz, jstring path,jstring mimeType, jobject client) java中String相應(yīng)jni中的jstring,java中的android.media.MediaScannerClient相應(yīng)jobject
2.4.2jni函數(shù)方法簽名 通過(guò)類型之間的相應(yīng)關(guān)系,jni能夠和java類型一一相應(yīng)。那么jni怎樣定位java的方法?
就是通過(guò)方法簽名。
方法簽名就是用一個(gè)字符串表示一個(gè)方法的參數(shù)類型和返回值,規(guī)則例如以下: (參數(shù)1類型簽名參數(shù)2類型簽名...參數(shù)n類型簽名)返回值類型簽名 注意:參數(shù)類型簽名中間沒(méi)有空格參數(shù)類型簽名有以下相應(yīng)關(guān)系
分為4類 1 原生數(shù)據(jù)類型boolean,byte,char,short,int ,long,float,double 這些分別用一個(gè)字母表示 2類,L+"全限定類名稱"+";" ?,這里要將"."替換成"/",比方String,相應(yīng)簽名類型 ?"Ljava/lang/String;" 3 數(shù)組,[+"元素的類型簽名",比方 int[] 相應(yīng)參數(shù)簽名為"[I", String[]相應(yīng)參數(shù)簽名為"[Ljava/lang/String;" 4 返回值假設(shè)是void。則用V表示 看一個(gè)樣例: { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native } 函數(shù)簽名為(IILjava/lang/String;Ljava/lang/String;)I 返回參數(shù)簽名為I,相應(yīng)java的int 參數(shù)列表簽名為(IILjava/lang/String;Ljava/lang/String;)相應(yīng)參數(shù)列表為(int,int,String,String) 所以在Log類中的println_native函數(shù)的聲明: int?println_native(int,int,String,String) 再看一個(gè)樣例 {"processFile","(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V"(void *)android_media_MediaScanner_processFile } 函數(shù)簽名為(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V 返回參數(shù)簽名為V。相應(yīng)java的void
參數(shù)列表簽名為(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;),相應(yīng)參數(shù)列表為(String,String,android/media/MediaScannerClient) 能夠找到MediaScanner類中的processFile函數(shù)的聲明 void?MediaScanner?(String,String,android.media.MediaScannerClient)
2.5 jni層操作java對(duì)象 java對(duì)象中有哪些:成員變量和成員函數(shù),那么jni中操作java對(duì)象也是操作變量和函數(shù)。實(shí)際在JNIEnv定義中中我們已經(jīng)看見(jiàn)非常多函數(shù)能夠進(jìn)行這兩個(gè)操作了。
要操作對(duì)象。就要找到該對(duì)象的類信息,jni中主要使用以下兩個(gè)方法: jclass Findclass(const char* name)//查找全路徑名稱為name的類信息 jclass GetObjectClass(jobject ojb)//返回該對(duì)象所在類的信息 比方:? jclass clazz = env->FindClass("android/util/Log");
在jni中。使用jfieldID和jmethodID來(lái)表示java中的成員變量和成員函數(shù)。能夠通過(guò)以下的方式獲得到: jfieldID GetFieldID(jclass clazz,const char* name,const char*sig); clazz:該類信息 name:變量名稱 sig:變量參數(shù)簽名 如:android_media_MediaScanner_native_init中的 fields.context =env->GetFieldID(clazz, "mNativeContext", "I"); 就是訪問(wèn)clazz對(duì)象中類型為int的mNativeContext變量。將其賦值給fields.context變量保存。
獲得jmethodID的方法: jmethodID GetMethodID(jclass clazz,const char* name,const char*sig); clazz:該類信息 name:函數(shù)名稱 sig:函數(shù)簽名 如: MyMediaScannerClient(JNIEnv *env, jobject client){ ..jclass mediaScannerClientInterface =env->FindClass(kClassMediaScannerClient);//相應(yīng)MediaScannerClie中void scanFile(String,long,long,boolean,boolean)方法 mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface,"scanFile","(Ljava/lang/String;JJZZ)V");//相應(yīng)MediaScannerClie中void handleStringTag (String,String)方法 mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface,"handleStringTag","(Ljava/lang/String;Ljava/lang/String;)V");//相應(yīng)MediaScannerClie中 void setMimeType(String)方法 mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface,"setMimeType","(Ljava/lang/String;)V"); ... } 有了jfieldID和jmethodID之后就能夠直接訪問(wèn)變量和函數(shù)了。
使用jfieldID static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz) {//fields.context =env->GetFieldID(clazz, "mNativeContext", "I"); return (MediaScanner *) env->GetIntField(thiz, fields.context); } 能夠利用這種方法,訪問(wèn)在thiz類中mNativeContext的值。 類似的方法還有非常多: GetTypeField(jobject,jfieldID)//返回jobject類中變量為jfieldID的變量 與得到變量相應(yīng)的就是設(shè)置變量的值 //fields.context =env->GetFieldID(clazz, "mNativeContext", "I"); env->SetIntField(thiz, fields.context, 0);//將mNativeContext設(shè)置為0 類似的: SetTypeField(jobject obj,jfieldID fieldID,nativeType value)//fieldID的值設(shè)置為value
使用jmethodID? scanFile(const char* path, long long lastModified,long long fileSize, bool isDirectory, bool noMedia)中的 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,fileSize, isDirectory, noMedia); 類似這樣的還有: CallTypeMethod(jobject obj,jmethodID methodID,參數(shù)1,參數(shù)2...)來(lái)調(diào)用jmethodID函數(shù),并將參數(shù)傳遞進(jìn)去。
這是對(duì)于對(duì)象的成員變量和成員函數(shù),假設(shè)是類級(jí)別的,加上statickeyword就可以 GetStaticFieldID(jclass clazz,const char* name,const char*sig); GetStaticTypeField(jobject obj,jfieldID fieldID) SetStaticTypeField(jobject obj,jfieldID fieldID,nativeType value)GetStaticMethodID(jclass clazz,const char* name,const char*sig); CallStaticTypeMethod(jobject obj,jmethodID methodID,參數(shù)1,參數(shù)2...) 相應(yīng)關(guān)系:
2.6 垃圾回收 java層的垃圾回收由垃圾回收器來(lái)進(jìn)行。可是jni層呢? 在java層。每個(gè)對(duì)象維護(hù)一個(gè)該對(duì)象的引用計(jì)數(shù)。假設(shè)對(duì)象被賦值為一個(gè)引用類型,則引用計(jì)數(shù)加一。可是在jni層中不是會(huì)導(dǎo)致該計(jì)數(shù)加一: static jobject save_class = NULL//定義了一個(gè)全局的jobject static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,jstring mimeType, jobject client) {....//保存thiz對(duì)象,也就是MediaScanner對(duì)象save_class =thiz ;... return ; }//調(diào)用call方法void call() {//使用save_class 能夠嗎?//不能夠,由于有可能MediaScanner對(duì)象已經(jīng)被回收了。 }
引用計(jì)數(shù)并沒(méi)有添加,MediaScanner有可能被回收。為了解決問(wèn)題,jni提出了三類引用:local reference,global reference,weak global reference 1 local reference:本地引用,能夠添加引用計(jì)數(shù)。作用范圍為本線程。一旦jni函數(shù)返回,這些引用就會(huì)被回收
2 global reference:全局應(yīng)用,能夠添加引用計(jì)數(shù),作用范圍多線程。須要顯示釋放。假設(shè)不釋放。永遠(yuǎn)不會(huì)被回收。 3 weak global reference:弱全局引用,不添加引用計(jì)數(shù),作用范圍為多線程。
須要顯示釋放。可是及時(shí)沒(méi)有釋放,也可能被虛擬機(jī)回收。
經(jīng)常使用的是local reference和global reference: 先看local reference boolean test(const char* name) {for(int i=0;i<10000;i++) { jstring nameStr = env->NewStringUTF(name); //假設(shè)這里我們不馬上釋放nameStr。那么會(huì)在函數(shù)結(jié)束之后才釋放。看起來(lái)沒(méi)有太大差別,若像這樣創(chuàng)建10000個(gè)jstring 就占用了許多的內(nèi)存了。所以要這里在不須要使用的時(shí)候還是及時(shí)釋放 //env->DeleteLocalRef(nameStr); } }
再看global reference: public:MyMediaScannerClient(JNIEnv *env, jobject client): mEnv(env),mClient(env->NewGlobalRef(client)),//創(chuàng)建一個(gè)全局引用mClient mScanFileMethodID(0),mHandleStringTagMethodID(0),mSetMimeTypeMethodID(0){LOGV("MyMediaScannerClient constructor");jclass mediaScannerClientInterface =env->FindClass(kClassMediaScannerClient);if (mediaScannerClientInterface == NULL) {LOGE("Class %s not found", kClassMediaScannerClient);} else {mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface,"scanFile","(Ljava/lang/String;JJZZ)V");mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface,"handleStringTag","(Ljava/lang/String;Ljava/lang/String;)V");mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface,"setMimeType","(Ljava/lang/String;)V");}}virtual ~MyMediaScannerClient(){LOGV("MyMediaScannerClient destructor");//在析構(gòu)函數(shù)中主動(dòng)釋放mClient mEnv->DeleteGlobalRef(mClient);}
2.7 異常 2.7.1檢測(cè)異常 使用jni中的ExceptionOccurred()函數(shù)來(lái)推斷
2.7.2處理異常 兩種方式 1.馬上返回,該異常在java層拋出,所以要在java層處理異常,否則程序異常退出 2.使用ExceptionClear來(lái)清除異常 static void android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path,jstring mimeType, jobject client) {...// Lock already hold by processDirectoryMediaScanner *mp = getNativeScanner_l(env, thiz);if (mp == NULL) {jniThrowException(env, kRunTimeException, "No scanner available");return;}.. }
調(diào)用了jniThrowException。剛函數(shù)在:dalvik\libnativehelper\JNIHelper.cpp extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {JNIEnv* e = reinterpret_cast<JNIEnv*>(env);if ((*env)->ExceptionCheck(e)) {/* TODO: consider creating the new exception with this as "cause" */scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));(*env)->ExceptionClear(e);if (exception.get() != NULL) {char* text = getExceptionSummary(env, exception.get());LOGW("Discarding pending exception (%s) to throw %s", text, className);free(text);}}scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));if (exceptionClass.get() == NULL) {LOGE("Unable to find exception class %s", className);/* ClassNotFoundException now pending */return -1;}if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {LOGE("Failed throwing '%s' '%s'", className, msg);/* an exception, most likely OOM, will now be pending */return -1;}return 0; }
總結(jié)
- 上一篇: linux centos6.5 ftp
- 下一篇: SDL2 自建对话框