android源碼-深刻理解JNI技術

9/5/2016 2:10:30 PMjava

android源碼-深刻理解JNI技術

本章涉及的源代碼文件名及位置android

AndroidRunTime.cpp (framework/base/core/jni/AndroidRunTime.cpp)
JNIHelp.c (libnativehelper/JNIHelp.c)

源碼的下載可參考上一篇博客:android源碼-下載與管理程序員

1.1 概述

JNI,是Java Native Interface的縮寫,中文爲Java本地調用。通俗地說,JNI是一種技術,經過這種技術能夠作到如下兩點:數組

  • Java程序中的函數能夠調用Native語言寫的函數,Native通常指的是C/C++編寫的函數。
  • Native程序中的函數能夠調用Java層的函數,也就是在C/C++程序中能夠調用Java的函數。

在Android平臺上,JNI就是一座將Native世界和Java世界間的天塹變成通途的橋,它展現了Android平臺上JNI所處的位置:網絡

Android平臺中JNI示意圖

由上圖可知,JNI將Java世界和Native世界緊密地聯繫在一塊兒了。在Android平臺上盡情使用Java開發的程序員們不要忘了,若是沒有JNI的支持,咱們將步履維艱!函數

1.2 Java世界-互通JNI

Java世界與Native世界經過JNI互通,Java世界與JNI之間創建直接互統統道,主要須要完成了兩個比較重要的要點工具

(1) 加載JNI庫編碼

Java要調用Native函數,就必須經過一個位於JNI層的動態庫才能作到。顧名思義,動態庫就是運行時加載的庫,那麼是何時,在什麼地方加載這個庫呢?.net

這個問題沒有標準答案,原則上是在調用native函數前,任什麼時候候、任何地方加載均可以。通行的作法是,在類的static語句中加載,經過調用System.loadLibrary方法就能夠了。System.loadLibrary函數的參數是動態庫的名字,即xxx_jni。系統會自動根據不一樣的平臺拓展成真實的動態拓展名,例如在Linux系統上會拓展成xxx_jni.so,而在Windows平臺上則會拓展成xxx_jni.dll線程

(2) Java的native函數調用

Java函數前都有Java的關鍵字native,它表示這兩個函數將由JNI層來實現。 因此對於Java程序員來講,使用JNI技術真的是太容易了。不過JNI層可沒這麼輕鬆,下面來看MS的JNI層分析。

1.3 Native世界-互通JNI

對於JNI橋接來說,最大的疑惑多是,怎麼會知道Java層的native_xx函數對應的是JNI層的什麼函數呢,對應關係是什麼?

1.2.1 註冊JNI函數

上面的問題其實討論的是JNI函數的註冊問題,「註冊」之意就是將Java層的native函數和JNI層對應的實現函數關聯起來,有了這種關聯,調用Java層的native函數時,就能順利轉到JNI層對應的函數執行了。而JNI函數的註冊實際上有兩種方法,下面分別作介紹。

(1) 靜態方法調用

咱們從網上找到的與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的樣式。

javah com.xio.HelloWorld

獲得以下聲明:

#include <jni.h>

#ifndef _Included_com_xio_HelloWorld
#define _Included_com_xio_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif

/*
* Class: com_xio_HelloWorld
* Method: printJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xio_HelloWorld_print_ljni
(JNIEnv *, jobject, jstring);


#ifdef __cplusplus
}
#endif
#endif

從上面代碼中能夠發現,printJNI的JNI層函數被聲明成:

// Java層函數名中若是有一個」_」的話,轉換成JNI後就變成了」_l」。	
JNIEXPORT jstring JNICALL Java_com_xio_HelloWorld_print_ljni

需解釋一下,靜態方法中native函數是如何找到對應的JNI函數的。其實,過程很是簡單:

  • 當Java層調用print_jni函數時,它會從對應的JNI庫Java_com_xio_HelloWorld_print_ljni,若是沒有,就會報錯。若是找到,則會爲這個print_jniJava_com_xio_HelloWorld_print_ljni創建一個關聯關係,其實就是保存JNI層函數的函數指針。之後再調用print_jni函數時,直接使用這個函數指針就能夠了,固然這項工做是由虛擬機完成的。

從這裏能夠看出,靜態方法就是根據函數名來創建Java函數和JNI函數之間的關聯關係的,它要求JNI層函數的名字必須遵循特定的格式。這種方法也有幾個弊端,它們是:

  • 須要編譯全部聲明瞭native函數的Java類,每一個生成的class文件都得用javah生成一個頭文件。
  • javah生成的JNI層函數名特別長,書寫起來很不方便。
  • 初次調用native函數時要根據函數名字搜索對應的JNI層函數來創建關聯關係,這樣會影響運行效率。

有什麼辦法能夠克服上面三種弊端嗎?根據上面的介紹,Java native函數是經過函數指針來和JNI層函數創建關聯關係的。若是直接讓native函數知道JNI層對應函數的函數指針,不就萬事大吉了嗎?這就是下面要介紹的第二種方法:動態註冊法。

(2) 動態註冊

既然Java native函數數和JNI函數是一一對應的,那麼是否是會有一個結構來保存這種關聯關係呢?答案是確定的。在JNI技術中,用來記錄這種一一對應關係的,是一個叫JNINativeMethod的結構,其定義以下:

typedef struct {
	constchar* name;    

    const char* signature;
	void*       fnPtr;
} JNINativeMethod;

// 第一個變量name是Java中函數的名字。
// 第二個變量signature,用字符串是描述了Java中函數的參數和返回值
// 第三個變量fnPtr是函數指針,指向native函數。前面都要接 (void *)

應該如何使用這個結構體呢?來看JNI層是如何作的,代碼以下所示:

//定義一個JNINativeMethod數組,其成員就是MS中全部native函數的一一對應關係。

static JNINativeMethod gMethods[] = {

	// Java中native函數的函數名。
	"print_jni" 
	
	//print_jni的簽名信息
	"()V",  
	 
	//JNI層對應函數指針。
	(void*)com_xio_HelloWorld_printJni 

};

int com_xio_printJni(JNIEnv*env) {
	
	//調用AndroidRuntime的registerNativeMethods函數	
	returnAndroidRuntime::registerNativeMethods(env,
               "com/xio/HelloWorld", gMethods, NELEM(gMethods));

}

AndroidRunTime類提供了一個registerNativeMethods函數來完成註冊工做,下面看registerNativeMethods的實現,代碼以下:

[-->AndroidRunTime.cpp]

int AndroidRuntime::registerNativeMethods(JNIEnv*env, constchar* className, const JNINativeMethod* gMethods, int numMethods) {

    //調用jniRegisterNativeMethods函數完成註冊

    returnjniRegisterNativeMethods(env, className, gMethods, numMethods);
}

其中jniRegisterNativeMethods是Android平臺中,爲了方便JNI使用而提供的一個幫助函數,其代碼以下所示:

[-->JNIHelp.c]

int jniRegisterNativeMethods(JNIEnv* env, constchar* className,                  constJNINativeMethod* gMethods, int numMethods) {
    jclassclazz;

    clazz= (*env)->FindClass(env, className);

	......

	//其實是調用JNIEnv的RegisterNatives函數完成註冊的

    if((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
       return -1;
    }

    return0;

}

其實動態註冊的工做,只用兩個函數就能完成。總結以下:

jclass clazz =  (*env)->FindClass(env, className);

//調用JNIEnv的RegisterNatives函數,註冊關聯關係。

(*env)->RegisterNatives(env, clazz, gMethods,numMethods);

當Java層經過System.loadLibrary加載完JNI動態庫後,緊接着會查找該庫中一個叫JNI_OnLoad的函數,若是有,就調用它,而動態註冊的工做就是在這裏完成的。

因此,若是想使用動態註冊方法,就必需要實現JNI_OnLoad函數,只有在這個函數中,纔有機會完成動態註冊的工做。靜態註冊則沒有這個要求,可我建議讀者也實現這個JNI_OnLoad函數,由於有一些初始化工做是能夠在這裏作的。

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

   //該函數的第一個參數類型爲JavaVM,這但是虛擬機在JNI層的表明喔,每一個Java進程只有一個

   JNIEnv* env = NULL;

    if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
         goto bail;

    }
	assert(env != NULL);

    if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0) {
		goto bail;
	}

	return JNI_VERSION_1_4;//必須返回這個值,不然會報錯。
}

JNI函數註冊的內容介紹完了。

注意:JNI層代碼中通常要包含jni.h這個頭文件。Android源碼中提供了一個幫助頭文件JNIHelp.h,它內部其實就包含了jni.h,因此咱們在本身的代碼中直接包含這個JNIHelp.h便可。

1.2.2 基本類型的轉換

經過前面的分析,解決了JNI函數的註冊問題。下面來研究數據類型轉換的問題。在Java中調用native函數傳遞的參數是Java數據類型,那麼這些參數類型到了JNI層會變成什麼呢?

Java數據類型分爲基本數據類型引用數據類型兩種,JNI層也是區別對待這兩者的。先來看基本數據類型的轉換。

基本類型的轉換很簡單,可用表1表示:

表1  基本數據類型轉換關係表

|Java |Native類型 |符號屬性 |字長 |--| |boolean |jboolean |無符號 |8位 |byte |jbyte |無符號 |8位 |char |jchar |無符號 |16位 |short |jshort |有符號 |16位 |int |jint |有符號 |32位 |long |jlong |有符號 |64位 |float |jfloat |有符號 |32位 |double |jdouble |有符號 |64位

上面列出了Java基本數據類型和JNI層數據類型對應的轉換關係,很是簡單。不過,應務必注意,轉換成Native類型後對應數據類型的字長,例如jchar在Native語言中是16位,佔兩個字節,這和普通的char佔一個字節的狀況徹底不同。

接下來看Java引用數據類型的轉換,引用數據類型的轉換如表2-2所示:

表2-2  Java引用數據類型轉換關係表

|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[] |floatArray |byte[] |jbyteArray |double[] |jdoubleArray |java.lang.Throwable實例|jthrowable

由上表可知:除了Java中基本數據類型的數組、Class、String和Throwable外,其他全部Java對象的數據類型在JNI中都用jobject表示。這一點太讓人驚訝了!

1.2.3 JNIEnv介紹

JNIEnv是一個和線程相關的,表明JNI環境的結構體,如圖展現了JNIEnv的內部結構:

JNIEnv內部結構簡圖

從上圖可知,JNIEnv實際上就是提供了一些JNI系統函數。經過這些函數能夠作到:

  • 調用Java的函數。
  • 操做jobject對象等不少事情。

這裏,先介紹一個關於JNIEnv的重要知識點。

上面提到說JNIEnv,是一個和線程有關的變量。也就是說,線程A有一個JNIEnv,線程B有一個JNIEnv。因爲線程相關,因此不能在線程B中使用線程A的JNIEnv結構體。讀者可能會問,JNIEnv不都是native函數轉換成JNI層函數後由虛擬機傳進來的嗎?使用傳進來的這個JNIEnv總不會錯吧?是的,在這種狀況下使用固然不會出錯。不過當後臺線程收到一個網絡消息,而又須要由Native層函數主動回調Java層函數時,JNIEnv是從何而來呢?根據前面的介紹可知,咱們不能保存另一個線程的JNIEnv結構體,而後把它放到後臺線程中來用。這該如何是好?

還記得前面介紹的那個JNI_OnLoad函數嗎?它的第一個參數是JavaVM,它是虛擬機在JNI層的表明,代碼以下所示:

//全進程只有一個JavaVM對象,因此能夠保存,任何地方使用都沒有問題。
jint JNI_OnLoad(JavaVM* vm, void* reserved)

正如上面代碼所說,不論進程中有多少個線程,JavaVM倒是獨此一份,因此在任何地方均可以使用它。那麼,JavaVM和JNIEnv又有什麼關係呢?答案以下:

  • 調用JavaVM的AttachCurrentThread函數,就可獲得這個線程的JNIEnv結構體。這樣就能夠在後臺線程中回調Java函數了。
  • 另外,後臺線程退出前,須要調用JavaVM的DetachCurrentThread函數來釋放對應的資源。

再來看JNIEnv的做用。

1.2.4 經過JNIEnv操做jobject

前面提到過一個問題,即Java的引用類型除了少數幾個外,最終在JNI層都用jobject來表示對象的數據類型,那麼該如何操做這個jobject呢?

從另一個角度來解釋這個問題。一個Java對象是由什麼組成的?固然是它的成員變量和成員函數了。那麼,操做jobject的本質就應當是操做這些對象的成員變量和成員函數。因此應先來看與成員變量及成員函數有關的內容。

(1) jfieldID 和jmethodID的介紹

咱們知道,成員變量和成員函數是由類定義的,它是類的屬性,因此在JNI規則中,用jfieldID和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爲這個函數和變量的簽名信息。如前所示,成員函數和成員變量都是類的信息,這兩個函數的第一個參數都是jclass。

在上面代碼中,將scanFile和handleStringTag函數的jmethodID保存爲MyMediaScannerClient的成員變量。爲何這裏要把它們保存起來呢?這個問題涉及一個事關程序運行效率的知識點:

· 若是每次操做jobject前都去查詢jmethoID或jfieldID的話將會影響程序運行的效率。因此咱們在初始化的時候,就能夠取出這些ID並保存起來以供後續使用。

取出jmethodID後,又該怎麼用它呢?

(2) 使用jfieldID和jmethodID

經過JNIEnv輸出的CallVoidMethod,再把jobject、jMethodID和對應參數傳進去,JNI層就可以調用Java對象的函數了!

實際上JNIEnv輸出了一系列相似CallVoidMethod的函數,形式以下:

NativeType Call<type>Method(JNIEnv *env,jobject obj,jmethodID methodID, ...)

其中type是對應Java函數的返回值類型,例如CallIntMethod、CallVoidMethod等。

上面是針對非static函數的,若是想調用Java中的static函數,則用JNIEnv輸出的CallStatic<Type>Method系列函數。

如今,咱們已瞭解瞭如何經過JNIEnv操做jobject的成員函數,那麼怎麼經過jfieldID操做jobject的成員變量呢?這裏,直接給出總體解決方案,以下所示:

//得到fieldID後,可調用Get<type>Field系列函數獲取jobject對應成員變量的值。

NativeType Get<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID)

//或者調用Set<type>Field系列函數來設置jobject對應成員變量的值。

void Set<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID,NativeType value)

下面咱們列出一些參加的Get/Set函數

|Get |Set| |--| |GetObjectField() |SetObjectField()| |GetBooleanField() |SetBooleanField()| |GetByteField() |SetByteField()| |GetCharField() |SetCharField()| |GetShortField() |SetShortField()| |GetIntField() |SetIntField()| |GetLongField() |SetLongField()| |GetFloatField() |SetFloatField()| |GetDoubleField() |SetDoubleField()|

經過本節的介紹,相信讀者已瞭解jfieldIDjmethodID的做用,也知道如何經過JNIEnv的函數來操做jobject了。雖然jobject是透明的,但有了JNIEnv的幫助,仍是能輕鬆操做jobject背後的實際對象了。

1.2.5 jstring介紹

Java中的String也是引用類型,不過因爲它的使用很是頻繁,因此在JNI規範中單首創建了一個jstring類型來表示Java中的String類型。雖然jstring是一種獨立的數據類型,可是它並無提供成員函數供操做。相比而言,C++中的string類就有本身的成員函數了。那麼該怎麼操做jstring呢?仍是得依靠JNIEnv提供的幫助。這裏看幾個有關jstring的函數:

  • 調用JNIEnv的NewString(JNIEnv env, const jcharunicodeChars,jsize len),能夠從Native的字符串獲得一個jstring對象。其實,能夠把一個jstring對象當作是Java中String對象在JNI層的表明,也就是說,jstring就是一個Java String。但因爲Java String存儲的是Unicode字符串,因此NewString函數的參數也必須是Unicode字符串。

  • 調用JNIEnv的NewStringUTF將根據Native的一個UTF-8字符串獲得一個jstring對象。在實際工做中,這個函數用得最多。

  • 上面兩個函數將本地字符串轉換成了Java的String對象,JNIEnv還提供了GetStringChars和GetStringUTFChars函數,它們能夠將Java String對象轉換成本地字符串。其中GetStringChars獲得一個Unicode字符串,而GetStringUTFChars獲得一個UTF-8字符串。

  • 另外,若是在代碼中調用了上面幾個函數,在作完相關工做後,就都須要調用ReleaseStringChars或ReleaseStringUTFChars函數對應地釋放資源,不然會致使JVM內存泄露。這一點和jstring的內部實現有關係,讀者寫代碼時務必注意這個問題。

1.2.6 JNI類型簽名的介紹

先來看動態註冊中的一段代碼:

static JNINativeMethod gMethods[] = {

	// Java中native函數的函數名。
	"print_jni" 
	
	//print_jni的簽名信息
	"()V",  
	 
	//JNI層對應函數指針。
	(void*)com_xio_HelloWorld_printJni 

};

上面代碼中的JNINativeMethod已經見過了,不過其中那個字符串"()V",根據前面的介紹可知,它是Java中對應函數的簽名信息,由參數類型和返回值類型共同組成。不過爲何須要這個簽名信息呢?

  • 這個問題的答案比較簡單。由於Java支持函數重載,也就是說,能夠定義同名但不一樣參數的函數。但僅僅根據函數名,是無法找到具體函數的。爲了解決這個問題,JNI技術中就使用了參數類型和返回值類型的組合,做爲一個函數的簽名信息,有了簽名信息和函數名,就能很順利地找到Java中的函數了。

JNI規範定義的函數簽名信息看起來很彆扭,不過習慣就行了。它的格式是:

(參數1類型標示參數2類型標示...參數n類型標示)返回值類型標示。

當參數的類型是引用類型時,其格式是」L包名;」,其中包名中的」.」換成」/」。上面例子中的

Ljava/lang/String;表示是一個Java String類型。

函數簽名不只看起來麻煩,寫起來更麻煩,稍微寫錯一個標點就會致使註冊失敗。因此,在具體編碼時,讀者能夠定義字符串宏,這樣改起來也方便。

表1  類型標示示意表

|類型標示 |Java類型 |類型標示 |Java類型| |--| |Z |boolean |F |float| |B |byte |D |double| |C |char |L/java/langaugeString; |String| |S |short |[I |int[]| |I |int |[L/java/lang/object; |Object[]| |J |long

上面列出了一些經常使用的類型標示。請讀者注意,若是Java類型是數組,則標示中會有一個「[」,另外,引用類型(除基本類型的數組外)的標示最後都有一個「;」。

再來看一些小例子,如表2所示:

表2 函數簽名小例子

|函數簽名 |Java函數 | |--| |「()Ljava/lang/String;」 |String f() | |「(ILjava/lang/Class;)J」|long f(int i, Class c)| |「([B)V」 |void f(byte[] bytes)|

雖然函數簽名信息很容易寫錯,但Java提供一個叫javap的工具能幫助生成函數或變量的簽名信息,它的用法以下:

javap –s -p xxx。其中xxx爲編譯後的class文件,s表示輸出內部數據類型的簽名信息,p表示打印全部函數和成員的簽名信息,而默認只會打印public成員和函數的簽名信息。

有了javap,就不用死記硬背上面的類型標示了。

1.2.7 垃圾回收

咱們知道,Java中建立的對象最後是由垃圾回收器來回收和釋放內存的,可它對JNI有什麼影響呢?下面看一個例子:

[-->垃圾回收例子]

static jobject save_thiz = NULL; //定義一個全局的jobject

static void com_xio_HelloWorld_print_jni(JNIEnv*env, jobject thiz, jstring path,
 jstringmimeType, jobject client) {

//保存Java層傳入的jobject對象,表明MediaScanner對象

save_thiz = thiz;

return;

}

//假設在某個時間,有地方調用callMediaScanner函數

void function1()	{

  //在這個函數中操做save_thiz,會有問題嗎?

}

上面的作法確定會有問題,由於和save_thiz對應的Java層中的對象頗有可能已經被垃圾回收了,也就是說,save_thiz保存的這個jobject多是一個野指針,如使用它,後果會很嚴重。

可能有人要問,將一個引用類型進行賦值操做,它的引用計數不會增長嗎?而垃圾回收機制只會保證那些沒有被引用的對象纔會被清理。問得對,但若是在JNI層使用下面這樣的語句,是不會增長引用計數的。

save_thiz = thiz; //這種賦值不會增長jobject的引用計數。

那該怎麼辦?沒必要擔憂,JNI規範已很好地解決了這一問題,JNI技術一共提供了三種類型的引用,它們分別是:

  • Local Reference:本地引用。在JNI層函數中使用的非全局引用對象都是Local Reference。它包括函數調用時傳入的jobject、在JNI層函數中建立的jobject。LocalReference最大的特色就是,一旦JNI層函數返回,這些jobject就可能被垃圾回收。

  • Global Reference:全局引用,這種對象如不主動釋放,就永遠不會被垃圾回收。

  • Weak Global Reference:弱全局引用,一種特殊的GlobalReference,在運行過程當中可能會被垃圾回收。因此在程序中使用它以前,須要調用JNIEnv的IsSameObject判斷它是否是被回收了。

平時用得最多的是Local Reference和Global Reference,下面看一個實例,代碼以下所示:

xxClient(JNIEnv *env, jobjectclient)
	:mEnv(env),
	
	//調用NewGlobalRef建立一個GlobalReference,這樣mClient就不用擔憂被回收了。

	mClient(env->NewGlobalRef(client)) {


}

//析構函數

virtual ~MyMediaScannerClient() {

	mEnv->DeleteGlobalRef(mClient);//調用DeleteGlobalRef釋放這個全局引用。
 }

每當JNI層想要保存Java層中的某個對象時,就可使用Global Reference,使用完後記住釋放它就能夠了。

由於Local Reference得回收並不是是及時的,若是沒有及時回收的Local Reference或許是進程佔用過多的一個緣由,請務必注意這一點。

1.2.8 JNI中的異常處理

JNI中也有異常,不過它和C++、Java的異常不太同樣。當調用JNIEnv的某些函數出錯後,會產生一個異常,但這個異常不會中斷本地函數的執行,直到從JNI層返回到Java層後,虛擬機纔會拋出這個異常。雖然在JNI層中產生的異常不會中斷本地函數的運行,但一旦產生異常後,就只能作一些資源清理工做了(例如釋放全局引用,或者ReleaseStringChars)。若是這時調用除上面所說函數以外的其餘JNIEnv函數,則會致使程序死掉。

JNI層函數能夠在代碼中截獲和修改這些異常,JNIEnv提供了三個函數進行幫助:

  • ExceptionOccured函數,用來判斷是否發生異常。

  • ExceptionClear函數,用來清理當前JNI層中發生的異常。

  • ThrowNew函數,用來向Java層拋出異常。

異常處理是JNI層代碼必須關注的事情,讀者在編寫代碼時務當心對待。

1.4 本章小結

本章經過一個實例介紹了JNI技術中的幾個重要方面,包括:

  • java層橋接JNI函數註冊的方法。

  • Native如何與JNI創建聯繫

  • Java和JNI層數據類型的轉換。

  • JNIEnv和jstring的使用方法,以及JNI中的類型簽名。

  • 最後介紹了垃圾回收在JNI層中的使用,以及異常處理方面的知識。

##參考文獻

《深刻理解Android卷I》

相關文章
相關標籤/搜索