關鍵類 | 路徑 |
---|---|
MediaScanner.java | frameworks/base/media/java/android/media/MediaScanner.java |
android_media_MediaScanner.cpp | frameworks/base/media/jni/android_media_MediaScanner.cpp |
android_media_MediaPlayer.cpp | frameworks/base/media/jni/android_media_MediaPlayer.cpp |
AndroidRuntime.cpp | frameworks/base/core/jni/AndroidRuntime.cpp |
JNI是Java Native Interface的縮寫,中文譯爲「Java本地調用」,通俗地說,JNI是一種技術,經過這種技術能夠作到如下兩點:html
✨ Java程序中的函數能夠調用Native語言寫的函數,Native通常指的是C/C++編寫函數;
✨ Native程序中的函數能夠調用Java層的函數,也就是說在C/C++程序中能夠調用Java的函數;java
在平臺無關的Java中,爲何要建立一個與Native相關的JNI技術呢?這豈不是破壞了Java的平臺無關特性嗎?JNI技術的推出主要有如下幾個方面的考慮:
✨ 承載Java世界的虛擬機是用Native語言寫的,而虛擬機又運行在具體的平臺上,因此虛擬機自己沒法作到平臺無關。然而,有了JNI技術後,就能夠對Java層屏蔽不一樣操做系統平臺之間的差別了。這樣,就能實現Java自己的平臺無關特性。
✨ 在Java誕生以前,不少程序都是用Native語言寫的,隨後Java後來受到追捧,而且迅速發展,可是做爲一門高級語言,沒法將軟件世界完全的改變。那麼既然Native模塊實現了許多功能,那麼在Java中直接經過JNI技術去使用它們不久能夠了?android
因此,咱們能夠把JNI看做一座將Native世界和Java世界互聯起來的一座橋樑(特殊說明:JNI層的代碼也是用Native寫的哦!)。數據庫
原理圖以下:數組
一概的講原理很枯燥,咱們直接以實際的代碼做爲範例來學習JNI的原理和實際使用!服務器
若是你是作Android系統開發和維護工做的,那麼你確定聽過MediaScanner,那咱們就拿它來舉例,看看它和JNI之間是如何關聯的。函數
(MediaScanner是Android平臺中多媒體系統的重要組成部分,它的功能是掃描媒體文件,獲得諸如歌曲時長、歌曲做者等媒體信息,並將他們存入到媒體數據庫中,拱其餘應用程序使用。)工具
MediaScanner和它的JNI:源碼分析
咱們簡單說明下這個流程圖:學習
✨ Java世界對應的是MediaScanner,而這個MediaScanner類有一些函數須要由Native層來實現(定義了一些Native函數,具體實現代碼在Native層)
✨ JNI層對應的是libmedia_jni.so。
· media_jni是JNI庫的名字,其中下劃線前的「media」是Native層庫的名字,這裏就是libmedia庫。下劃線後的「jni」表示它是一個JNI庫。
· Android平臺基本上都採用「lib模塊名_jni.so」來命名JNI庫。
✨ Native層對應的是libmedia.so,這個庫完成了實際的功能。
✨ MediaScanner將經過JNI庫libmedia_jni.so和Native層的libmedia.so交互。
咱們先來看看MediaScanner在Java層中關於JNI的代碼:
package android.media; public class MediaScanner implements AutoCloseable { static { // static語句 // 這個咱們以前說過,media_jni爲JNI庫的名字,實際加載動態庫的時候會將其拓展成libmedia_jni.so System.loadLibrary("media_jni"); native_init(); // 調用native_init函數 } ... ... private native void processFile(String path, String mimeType, MediaScannerClient client); ... ... private static native final void native_init(); // 申明一個native函數。native爲Java的關鍵字,表示它由JNI層實現。 ... ... }
OK,以上代碼列出了兩個重要的要點:(1)加載JNI庫;(2)調用Java的native函數
咱們前面說到過,若是Java要調用native函數,就必須經過一個位於JNI層的動態庫來實現。那麼這個動態庫在何時、什麼地方加載?
原則上,在調用native函數以前,咱們能夠在任什麼時候候、任何地方去加載動態庫。但通常通行的作法就是在類的static語句中加載,調用System.loadLibrary方法就能夠了。
咱們發現native_init和processFile函數前面都有Java的關鍵字native,這個就表示函數將由JNI層來實現。
因此在Java層面去使用JNI只要作兩項工做:(1)加載對應的JNI庫;(2)申明由關鍵字native修飾的函數。
接下來咱們看下Java層中定義的兩個native函數在JNI層的實現。
native_init的JNI層實現
static const char* const kClassMediaScanner = "android/media/MediaScanner"; static void android_media_MediaScanner_native_init(JNIEnv *env) { jclass clazz = env->FindClass(kClassMediaScanner); if (clazz == NULL) { return; } fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); if (fields.context == NULL) { return; } }
processFile的JNI層實現
static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) { // Lock already hold by processDirectory MediaScanner *mp = getNativeScanner_l(env, thiz); ... ... const char *pathStr = env->GetStringUTFChars(path, NULL); ... ... env->ReleaseStringUTFChars(path, pathStr); if (mimeType) { env->ReleaseStringUTFChars(mimeType, mimeTypeStr); } }
這邊咱們來解答一個問題,咱們確實是知道MediaScanner的native函數是JNI層去實現的,可是系統是如何知道Java層的native_init函數對應的就是JNI層的android_media_MediaScanner_native_init函數呢?
不知道你有沒有注意到native_init函數位於android.media這個包中,它的全路徑名應該是android.media.MediaScanner.native_init,而JNI層函數的名字是android_media_MediaScanner_native_init。
是否是很神奇?名字對應着,惟一的區別就是「.」這個符號變成了「_」。由於在Native語言中,符號「.」有着特殊的意義,因此JNI層須要把Java函數名稱(包括包名)中的「.」換成「_」。也就是經過這種方式,native_init找到了本身JNI層的本家兄弟android.media.MediaScanner.native_init。
咱們知道了Java層native函數對應JNI層的函數的原理,但有個問題,咱們知道是哪一個函數,可是想要把兩個函數關聯起來(也就是說去調用它)就涉及到JNI函數註冊的問題(不註冊,就沒有關聯,沒有關聯就沒法調用)。
這種方法很簡單,很暴力!直接根據函數名來找對應的JNI函數,它須要Java的工具程序javah參與,總體流程以下:
✨ 先編寫Java代碼,而後編譯生成.class文件。
✨ 使用Java的工具程序javah,採用命令「javah -o output packagename.classname」,這樣它會生成一個叫output.h的JNI層頭文件。其中packagename.classname是Java代碼編譯後的class文件,而在生成的output.h文件裏,聲明瞭對應的JNI層函數,只要實現裏面的函數便可。
這個頭文件的名字通常都會使用packagename_class.h的樣式,例如MediaScanner對應的JNI層頭文件就是android_media_MediaScanner.h。
/* DO NOT EDIT THIS FILE - it is machine generated*/ #include <jni.h> // 必須包含這個頭文件,不然編譯通不過 /* Header for class android_media_MediaScanner */ #ifndef _Included_android_media_MediaScanner #define _Included_android_media_MediaScanner #ifdef _cplusplus extern "C" { #endif ... ... // 略去一部份內容 // processFile對應的JNI函數 JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile(JNIEnv *, jobject, jstring, jstring, jobject); ... ... // 略去一部份內容 // native_init對應的JNI函數 JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit(JNIEnv *, jclass); #ifdef _cplusplus } #endif #endif
從上面代碼中能夠發現,native_init和processFile的JNI層函數被聲明成:
// Java 層函數名中若是由一個「_」, 轉換成JNI後就變成了「l」 JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit
Ok,那麼靜態方法中native函數是如何找到對應的JNI函數的呢?
當Java層調用native_init函數時,它會從對應的JNI庫中尋找Java_android_media_MediaScanner_native_init函數,若是沒有,就會報錯。若是找到,則會爲這個native_init和Java_android_media_MediaScanner_native_init創建一個關聯關係,其實就是保存JNI層函數的函數指針。之後再調用native_init函數時,直接使用這個函數指針就能夠了,固然這項工做是由虛擬機完成的。
從這裏能夠看出,靜態方法就是根據函數名來創建Java函數與JNI函數之間的關聯關係的,並且它要求JNI函數的名字必須遵循特定的格式。
這種方法有三個弊端,以下:
✨ 須要編譯全部聲明瞭native函數的Java類,每一個所生成的class文件都得用javah生成一個頭文件;
✨ javah生成的JNI層函數名特別長,書寫起來很不方便;
✨ 初次調用native函數時須要根據函數名稱搜索對應的JNI函數來創建關聯關係,這樣會影響運行效率。
因此咱們是否有辦法克服以上三點弊端?咱們知道靜態方法是去動態庫裏找一遍,而後創建關聯關係,之後再根據這個函數指針去調用對應的JNI函數,那麼若是咱們直接讓native函數直接知道JNI層對應函數的函數指針,是不就Ok了?
這就是下面咱們要介紹的第二種方法:動態註冊法!
咱們知道Java native函數和JNI函數是一一對應的,這個就像咱們key-value同樣,那麼若是有一個結構來保存這種關聯關係,那麼經過這個結構直接能夠找到彼此的關聯,是否是就效率就高多了?
答案是確定的,動態註冊就是這麼幹的!在JNI技術中,用來記錄這種一一對應關係的,是一個叫 JNINativeMethod 的結構,其定義以下:
typedef struct { char *name; // Java中native函數的名字,不用攜帶包的路徑,例如:native_init char *signature; // Java中函數的簽名信息,用字符串表示,是參數類型和返回值類型的集合 void *fnPtr; // JNI層對應函數的函數指針,注意它是 void* 類型 }JNINativeMethod;
下面咱們看看如何使用這個結構體,看下MediaScanner JNI層是如何作的。
// 定義一個JNINativeMethod數組,其成員就是MediaScanner中全部native函數的一一對應關係。 static const JNINativeMethod gMethods[] = { ... ... { "processFile", // Java中native函數的函數名 "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", // processFile的簽名信息 (void *)android_media_MediaScanner_processFile // JNI層對應的函數指針 }, ... ... { "native_init", "()V", (void *)android_media_MediaScanner_native_init }, ... ... };
是否是很一目瞭然?定義好了,不能直接用啊,固然須要註冊一下。
// This function only registers the native methods, and is called from // JNI_OnLoad in android_media_MediaPlayer.cpp // 註冊JNINativeMethod數組 int register_android_media_MediaScanner(JNIEnv *env) { // 調用AndroidRuntime的registerNativeMethods函數,第二個參數代表是Java中的哪一個類 // 咱們在講解Zygote原理時,聊過建立Java虛擬機,註冊JNI函數的內容 return AndroidRuntime::registerNativeMethods(env, kClassMediaScanner, gMethods, NELEM(gMethods)); }
AndroidRunTime類提供了一個registerNativeMethods函數來完成註冊的工做,下面來看下registerNativeMethods的實現:
/* * Register native methods using JNI. */ /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { // 調用jniRegisterNativeMethods函數完成註冊 return jniRegisterNativeMethods(env, className, gMethods, numMethods); }
其實,jniRegisterNativeMethods是Android平臺中爲了方便JNI使用而提供的一個幫助函數,其代碼以下:
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); ... ... // 其實是調用JNIEnv的RegisterNatives函數完成註冊的 if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return -1; } }
我知道你看到這邊已經頭疼了,調用來調用去,看上去很麻煩,是否是?其實動態註冊的工做,只用兩個函數就能完成,以下:
(1)jclass clazz = (*env)->FindClass(env, className);
env指向一個JNIEnv結構體,它很是重要,後面咱們會討論。classname爲對應的Java類名,因爲JNINativeMethod中使用的函數名並不是全路徑名,因此要指明是哪一個類。
(2)(*env)->RegisterNatives(env, clazz, gMethods, numMethods);
調用JNIEnv的RegisterNatives函數,註冊關聯關係。
那麼,你如今知道了若是動態註冊了,可是有個問題,這些動態註冊的函數在何時和什麼地方被調用?
當Java層經過System.loadLibrary加載完JNI動態庫後,緊接着就會去查找該庫中一個叫JNI_OnLoad的函數。若是有,就調用它,而動態註冊的工做就是在這裏完成的。
動態庫是libmedia_jni.so,那麼JNI_OnLoad函數在哪裏實現的?若是你看的比較自信的話,我相信以前代碼中有段註釋你應該注意到了。
// 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)); }
因爲多媒體系統不少地方都使用了JNI,因此JNI_OnLoad被放到了android_media_MediaPlayer.cpp中,咱們看下代碼:
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { // 該函數的第一個參數類型爲JavaVM,這但是虛擬機在JNI層的表明哦,每一個Java進程只有一個這樣的JavaVM JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { goto bail; } ... ... if (register_android_media_MediaScanner(env) < 0) { ALOGE("ERROR: MediaScanner native registration failed\n"); goto bail; } ... ... /* success -- return valid version number */ result = JNI_VERSION_1_4; bail: return result; }
在Java中調用native函數傳遞的參數是Java數據類型,那麼這些參數類型傳遞到JNI層會變成什麼類型?
Java數據類型分爲「基本數據類型」和「引用數據類型」兩種,JNI層也是區別對待二者的。
Java基本類型 | Native類型 | 符號屬性 | 字長 |
---|---|---|---|
boolean | jboolean | 無符號 | 8位 |
byte | jbyte | 無符號 | 8位 |
char | jchar | 無符號 | 16位 |
short | jshort | 有符號 | 16位 |
int | jint | 有符號 | 32位 |
long | jlong | 有符號 | 64位 |
float | jfloat | 有符號 | 32位 |
double | jdoublt | 有符號 | 64位 |
Java引用類型 | Native類型 | Java引用類型 | Native類型 |
---|---|---|---|
All objects | jobject | char[] | jcharArray |
java.lang.Class 實例 | jclass | short[] | jshortArray |
java.lang.String 實例 | jstring | int[] | jintArray |
Object[] | jobjectArray | long[] | jlongArray |
boolean[] | jbooleanArray | float | jfloatArray |
byte[] | jbyteArray | double[] | jdoubleArray |
java.lang.Throwable 實例 | jthrowable |
咱們舉例說明,看下processFile函數:
private native void processFile ( String path, String mimeType, MediaScannerClient client); static void android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)
咱們發現:
✨ Java的String類型在JNI層對應爲jstring類型;
✨ Java的MediaScannerClient類型在JNI層對應爲jobject。
不知道你有沒有注意到一個問題,Java中的processFile中只有三個參數,爲何到了JNI層對應的函數卻有五個參數?
static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)
接下來咱們開始重點討論JNIEnv!!!
JNIEnv的概念
是一個與線程相關的表明JNI環境的結構體,該結構體表明瞭Java在本線程的執行環境。
JNIEnv的做用
✨ 調用 Java 函數 : JNIEnv 表明 Java 執行環境, 可以使用 JNIEnv 調用 Java 中的代碼
✨ 操做 Java 對象 : Java 對象傳入 JNI 層就是 Jobject 對象, 需要使用 JNIEnv 來操做這個 Java 對象
咱們來看一個有趣的現象
前面,咱們已經知道 JNIEnv 是一個與線程相關的變量,若是此時線程 A 有一個 JNIEnv 變量, 線程 B 也有一個JNIEnv變量,因爲線程相關,因此 A 線程不能使用 B 線程的 JNIEnv 結構體變量。
此時,一個java對象經過JNI調用動態庫中的一個send()函數向服務器發送消息,不等服務器消息到來就當即返回,同時把JNI接口的指針JNIEnv *env(虛擬機環境指針),和jobject obj保存在動態庫中的變量裏。一段時間後,動態庫中的消息接收線程接收到服務器發來的消息,並試圖經過保存過的env和obj來調用先前的java對象的方法(至關於JAVA回調方法)來處理此消息,此時程序忽然退出(崩潰)。
爲何?
緣由:前臺JAVA線程發送消息,後臺線程處理消息,歸屬於兩個不一樣的線程,不能使用相同的JNIEnv變量。
怎麼解決?
還記得咱們前面介紹的JNI_OnLoad函數嗎?它的第一個參數是JavaVM,它是虛擬機在JNI層的表明!!!
// 全進程只有一個JavaVM對象,因此能夠在任何地方使用 jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
那麼也就是說,不論進程有多少線程(不論有多少JNIEnv),JavaVM倒是獨此一份!因此,咱們能夠利用一個機制:利用全局的 JavaVM 指針獲得當前線程的 JNIEnv 指針。
JavaVM和JNIEnv:
✨ 調用JavaVM的AttachCurrentThread函數,就可獲得這個線程的JNIEnv結構體,這樣就能夠在後臺線程中回調Java函數了。
✨ 另外,在後臺線程退出前,須要調用JavaVM的DetachCurrentThread函數來釋放對應的資源。
前面介紹數據類型的時候,咱們知道Java的引用類型除了少數幾個外(Class、String和Throwable),最終在JNI層都會用jobject來表示對象的數據類型,那麼該如何操做這個jobject呢?
咱們先回顧下Java對象是由什麼組成的?固然是它的成員變量和成員函數了!那麼同理,操做jobject的本質就應當是操做這些對象的成員變量和成員函數!那麼jobject的成員變量和成員函數又是什麼?
在java中,咱們知道成員變量和成員函數都是由類定義的,他們是類的屬性,那麼在JNI規則中,也是這麼來定義的,用jfieldID定義Java類的成員變量,用jmethodID定義Java類的成員函數。
可經過JNIEnv的下面兩個函數獲得:
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
其中,jclass表明Java類,name表示成員函數或成員變量的名字,sig爲這個函數和變量的簽名信息(後面會說到)。
咱們來看看在MediaScanner中如何使用它們,直接看代碼:android_media_MediaScanner.cpp::MyMediaScannerClient構造函數
class MyMediaScannerClient : public MediaScannerClient { public: MyMediaScannerClient(JNIEnv *env, jobject client)... ... { // 先找到android.media.MediaScannerClient類在JNI層中對應的jclass實例 jclass mediaScannerClientInterface = env->FindClass(kClassMediaScannerClient); if (mediaScannerClientInterface == NULL) { ALOGE("Class %s not found", kClassMediaScannerClient); } else { // 取出MediaScannerClient類中函數scanFile的jMethodID mScanFileMethodID = env->GetMethodID( mediaScannerClientInterface, "scanFile", "(Ljava/lang/String;JJZZ)V"); // 取出MediaScannerClient類中函數handleStringTag的jMethodID mHandleStringTagMethodID = env->GetMethodID( mediaScannerClientInterface, "handleStringTag", "(Ljava/lang/String;Ljava/lang/String;)V"); ... ... } } ... ... jobject mClient; jmethodID mScanFileMethodID; jmethodID mHandleStringTagMethodID; ... ... }
從上面的代碼中,將scanFile和handleStringTag函數的jMethodID保存在MyMediaScannerClient的成員變量中。爲何這裏要把它們保存起來呢?這個問題涉及到一個關於程序運行效率的知識點:
若是每次操做jobject前都要去查詢jmethodID或jfieldID,那麼將會影響程序運行的效率,因此咱們在初始化的時候能夠取出這些ID並保存起來以供後續使用。
咱們來看看android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile函數
virtual status_t scanFile(const char* path, long long lastModified, long long fileSize, bool isDirectory, bool noMedia) { jstring pathStr; if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { mEnv->ExceptionClear(); return NO_MEMORY; } /* * 調用JNIEnv的CallVoidMethod函數 * 注意CallVoidMethod的參數: *(1)第一個參數是表明MediaScannerClient的jobject對象 *(2)第二個參數是函數scanFile的jmethodID,後面是Java中的scanFile的參數 */ mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize, isDirectory, noMedia); mEnv->DeleteLocalRef(pathStr); return checkAndClearExceptionFromCallback(mEnv, "scanFile"); }
經過JNIEnv輸出CallVoidMethod,再把jobject、jMethodID和對應的參數傳進去,JNI層就可以調用Java對象的函數了!
實際上JNIEnv輸出了一系列相似CallVoidMethod的函數,形式以下:
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...)
其中type對應java函數的返回值類型,例如CallIntMethod、CallVoidMethod等。若是想調用Java中的static函數,則用JNIEnv輸出的CallStatic<Type>Method系列函數。
因此,咱們能夠看出,雖然jobject是透明的,但有了JNIEnv的幫助,仍是能輕鬆操做jobject背後的實際對象的。
這一節咱們單獨聊聊String。Java中的String也是引用類型,不過因爲它的使用頻率很高,因此在JNI規範中單首創建了一個jstring類型來表示Java中的String類型。
雖然jstring是一種獨立的數據累心,可是它並無提供成員函數以便操做。而C++中的string類是由本身的成員函數的。那麼該如何操做jstring呢?仍是得依靠JNIEnv提供幫助。
先看幾個有關jstring的函數:
✨ 調用JNIEnv的NewString(const jchar* unicodeChars, jsize len),能夠從Native的字符串獲得一個jstring對象。
✨ 調用JNIEnv的NewStringUTF(const char* bytes)將根據Native的一個UTF-8字符串獲得一個jstring對象。
✨ 上面兩個函數將本地字符串轉換成了Java的String對象,JNIEnv還提供了GetStringChars函數和GetStringUTFChars函數,它們能夠將Java String對象轉換成本地字符串。其中GetStringChars獲得一個Unicode字符串,而GetStringUTFChars獲得一個UTF-8字符串。
✨ 另外,若是在代碼中調用了上面幾個函數,在作完相關工做後,就都須要調用ReleaseStringChars函數或ReleaseStringUTFChars函數來對應地釋放資源,否定會致使JVM內存泄漏。
咱們看段代碼加深印象:
static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) { // Lock already hold by processDirectory MediaScanner *mp = getNativeScanner_l(env, thiz); ... ... 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; } ... ... }
咱們看下動態註冊中的一段代碼:
static const JNINativeMethod gMethods[] = { ... ... { "processFile", // processFile的簽名信息,這麼長的字符串,是什麼意思? "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processFile }, ... ... };
上面這段代碼咱們以前早就見過了,不過"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V"是什麼意思呢?
咱們前面提到過,這個是Java中對應函數的簽名信息,由參數類型和返回值類型共同組成,有人可能有疑問,這東西是幹嗎的?
咱們都知道,Java支持函數重載,也就是說,能夠定義同名但不一樣參數的函數。但僅僅根據函數名是無法找到具體函數的。爲了解決這個問題,JNI技術中就將參數類型和返回值類型的組合做爲一個函數的簽名信息,有了簽名信息和函數名,就能很順利地找到Java中的函數了。
JNI規範定義的函數簽名信息看起來很彆扭,不過習慣就行了。它的格式是:
(參數 1 類型標識參數 2 類型標識 ... 參數 n 類型標識) 返回值類型標識
咱們仍然拿processFile的例子來看下:
{ "processFile", // Java中的函數定義爲 private native void processFile(String path, String mimeType, MediaScannerClient client); // 對應的JNI函數簽名以下: "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", // void類型對應的標示是V // 當參數的類型是引用類型時,其格式是「L包名」,其中包名中的「.」換成「/」,Ljava/lang/String表示是一個Java String類型 (void *)android_media_MediaScanner_processFile },
【注意】:引用類型(除基本類型的數組外)的標識最後都有一個「;」。
函數簽名不只看起來麻煩,寫起來更麻煩,稍微寫錯一個標點都會致使註冊失敗,因此在具體編碼時,能夠定義字符串宏(這邊就很少作解釋了,能夠自行查詢瞭解便可)。
雖然函數簽名信息很容易寫錯,可是Java提供了一個叫javap的工具可以幫助咱們生成函數或變量的簽名信息,它的用法以下:
javap -s -p xxx
其中 xxx 爲編譯後的class文件,s表示輸出內部數據類型的簽名信息,p表示打印全部函數和成員的簽名信息,默認只會打印public成員和函數的簽名信息。
這部分我打算單獨放在一篇博文中探討,結果具體錯誤進行分析。