JNI

JNI全稱Java Native Interface。它爲託管代碼(使用Java編程語言編寫)與本地代碼(使用C/C++編寫)提供了一種交互方式。它是與廠商無關的(vendor-neutral),支持從動態共享庫中加載代碼,雖然這樣會稍顯麻煩,但有時這是至關有效的。html

若是你對JNI還不是太熟悉,能夠先通讀Java Native Interface Specification這篇文章來對JNI如何工做以及哪些特性可用有個大體的印象。這種接口的一些方面不能當即一讀就顯而易見,因此你會發現接下來的幾個章節頗有用處。java

JavaVM 及 JNIEnv

JNI定義了兩種關鍵數據結構,「JavaVM」和「JNIEnv」。它們本質上都是指向函數表指針的指針(在C++版本中,它們被定義爲類,該類包含一個指向函數表的指針,以及一系列能夠經過這個函數表間接地訪問對應的JNI函數的成員函數)。JavaVM提供「調用接口(invocation interface)」函數, 容許你建立和銷燬一個JavaVM。理論上你能夠在一個進程中擁有多個JavaVM對象,但安卓只容許一個。android

JNIEnv提供了大部分JNI功能。你定義的全部本地函數都會接收JNIEnv做爲第一個參數。shell

JNIEnv是用做線程局部存儲。所以,你不能在線程間共享一個JNIEnv變量。若是在一段代碼中沒有其它辦法得到它的JNIEnv,你能夠共享JavaVM對象,使用GetEnv來取得該線程下的JNIEnv(若是該線程有一個JavaVM的話;見下面的AttachCurrentThread)。編程

JNIEnv和JavaVM的在C聲明是不一樣於在C++的聲明。頭文件「jni.h」根據它是以C仍是以C++模式包含來提供不一樣的類型定義(typedefs)。所以,不建議把JNIEnv參數放到可能被兩種語言引入的頭文件中(換一句話說:若是你的頭文件須要#ifdef __cplusplus,你可能不得不在任何涉及到JNIEnv的內容處都要作些額外的工做)。數組

線程

全部的線程都是Linux線程,由內核統一調度。它們一般從託管代碼中啓動(使用Thread.start),但它們也可以在其餘任何地方建立,而後鏈接(attach)到JavaVM。例如,一個用pthread_create啓動的線程可以使用JNI AttachCurrentThread 或 AttachCurrentThreadAsDaemon函數鏈接到JavaVM。在一個線程成功鏈接(attach)以前,它沒有JNIEnv,不可以調用JNI函數緩存

鏈接一個本地環境建立的線程會觸發構造一個java.lang.Thread對象,而後其被添加到主線程羣組(main ThreadGroup),以讓調試器能夠探測到。對一個已經鏈接的線程使用AttachCurrentThread不作任何操做(no-op)。安全

安卓不能停止正在執行本地代碼的線程。若是正在進行垃圾回收,或者調試器已發出了停止請求,安卓會在下一次調用JNI函數的時候停止線程。網絡

鏈接過的(attached)線程在它們退出以前必須經過JNI調用DetachCurrentThread。若是你以爲直接這樣編寫不太優雅,在安卓2.0(Eclair)及以上, 你可使用pthread_key_create來定義一個析構函數,它將會在線程退出時被調用,你能夠在那兒調用DetachCurrentThread (使用生成的key與pthread_setspecific將JNIEnv存儲到線程局部空間內;這樣JNIEnv可以做爲參數傳入到析構函數當中去)。數據結構

jclass, jmethodID, jfieldID

若是你想在本地代碼中訪問一個對象的字段(field),你能夠像下面這樣作:

  • 對於類,使用FindClass得到類對象的引用
  • 對於字段,使用GetFieldId得到字段ID
  • 使用對應的方法(例如GetIntField)獲取字段下面的值

相似地,要調用一個方法,你首先得得到一個類對象的引用,而後是方法ID(method ID)。這些ID一般是指向運行時內部數據結構。查找到它們須要些字符串比較,但一旦你實際去執行它們得到字段或者作方法調用是很是快的。

若是性能是你看重的,那麼一旦查找出這些值以後在你的本地代碼中緩存這些結果是很是有用的。由於每一個進程當中的JavaVM是存在限制的,存儲這些數據到本地靜態數據結構中是很是合理的。

類引用(class reference),字段ID(field ID)以及方法ID(method ID)在類被卸載前都是有效的。若是與一個類加載器(ClassLoader)相關的全部類都可以被垃圾回收,可是這種狀況在安卓上是罕見甚至不可能出現,只有這時類才被卸載。注意雖然jclass是一個類引用,可是必需要調用NewGlobalRef保護起來(見下個章節)。

當一個類被加載時若是你想緩存些ID,然後當這個類被卸載後再次載入時可以自動地更新這些緩存ID,正確作法是在對應的類中添加一段像下面的代碼來初始化這些ID:

/* * 咱們在一個類初始化時調用本地方法來緩存一些字段的偏移信息 * 這個本地方法查找並緩存你感興趣的class/field/method ID * 失敗時拋出異常 */ private static native void nativeInit(); static { nativeInit(); } 

在你的C/C++代碼中建立一個nativeClassInit方法以完成ID查找的工做。當這個類被初始化時這段代碼將會執行一次。當這個類被卸載後然後再次載入時,這段代碼將會再次執行。

局部和全局引用

每一個傳入本地方法的參數,以及大部分JNI函數返回的每一個對象都是「局部引用」。這意味着它只在當前線程的當前方法執行期間有效。即便這個對象自己在本地方法返回以後仍然存在,這個引用也是無效的

這一樣適用於全部jobject的子類,包括jclass,jstring,以及jarray(當JNI擴展檢查是打開的時候,運行時會警告你對大部分對象引用的誤用)。

若是你想持有一個引用更長的時間,你就必須使用一個全局(「global」)引用了。NewGlobalRef函數以一個局部引用做爲參數而且返回一個全局引用。全局引用可以保證在你調用DeleteGlobalRef前都是有效的。

這種模式一般被用在緩存一個從FindClass返回的jclass對象的時候,例如:

jclass localClass = env->FindClass("MyClass"); jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass)); 

全部的JNI方法都接收局部引用和全局引用做爲參數。相同對象的引用卻可能具備不一樣的值。例如,用相同對象連續地調用NewGlobalRef獲得返回值多是不一樣的。爲了檢查兩個引用是否指向的是同一個對象,你必須使用IsSameObject函數。毫不要在本地代碼中用==符號來比較兩個引用。

得出的結論就是你毫不要在本地代碼中假定對象的引用是常量或者是惟一的。表明一個對象的32位值從方法的一次調用到下一次調用可能有不一樣的值。在連續的調用過程當中兩個不一樣的對象卻可能擁有相同的32位值。不要使用jobject的值做爲key.

開發者須要「不過分分配」局部引用。在實際操做中這意味着若是你正在建立大量的局部引用,或許是經過對象數組,你應該使用DeleteLocalRef手動地釋放它們,而不是寄但願JNI來爲你作這些。實現上只預留了16個局部引用的空間,因此若是你須要更多,要麼你刪掉之前的,要麼使用EnsureLocalCapacity/PushLocalFrame來預留更多。

注意jfieldID和jmethodID是映射類型(opaque types),不是對象引用,不該該被傳入到NewGlobalRef。原始數據指針,像GetStringUTFChars和GetByteArrayElements的返回值,也都不是對象(它們可以在線程間傳遞,而且在調用對應的Release函數以前都是有效的)。

還有一種不常見的狀況值得一提,若是你使用AttachCurrentThread鏈接(attach)了本地進程,正在運行的代碼在線程分離(detach)以前決不會自動釋放局部引用。你建立的任何局部引用必須手動刪除。一般,任何在循環中建立局部引用的本地代碼可能都須要作一些手動刪除。

UTF-八、UTF-16 字符串

Java編程語言使用UTF-16格式。爲了便利,JNI也提供了支持變形UTF-8(Modified UTF-8)的方法。這種變形編碼對於C代碼是很是有用的,由於它將\u0000編碼成0xc0 0x80,而不是0x00。最愜意的事情是你能在具備C風格的以\0結束的字符串上計數,同時兼容標準的libc字符串函數。很差的一面是你不能傳入隨意的UTF-8數據到JNI函數而還期望它正常工做。

若是可能的話,直接操做UTF-16字符串一般更快些。安卓當前在調用GetStringChars時不須要拷貝,而GetStringUTFChars須要一次分配而且轉換爲UTF-8格式。注意UTF-16字符串不是以零終止字符串,\u0000是被容許的,因此你須要像對jchar指針同樣地處理字符串的長度。

不要忘記Release你Get的字符串。這些字符串函數返回jchar或者jbyte,都是指向基本數據類型的C格式的指針而不是局部引用。它們在Release調用以前都保證有效,這意味着當本地方法返回時它們並不主動釋放。

傳入NewStringUTF函數的數據必須是變形UTF-8格式。一種常見的錯誤狀況是,從文件或者網絡流中讀取出的字符數據,沒有過濾直接使用NewStringUTF處理。除非你肯定數據是7位的ASCII格式,不然你須要剔除超出7位ASCII編碼範圍(high-ASCII)的字符或者將它們轉換爲對應的變形UTF-8格式。若是你沒那樣作,UTF-16的轉換結果可能不會是你想要的結果。JNI擴展檢查將會掃描字符串,而後警告你那些無效的數據,可是它們將不會發現全部潛在的風險。

原生類型數組

JNI提供了一系列函數來訪問數組對象中的內容。對象數組的訪問只能一次一條,但若是原生類型數組以C方式聲明,則可以直接進行讀寫。

爲了讓接口更有效率而不受VM實現的制約,GetArrayElements系列調用容許運行時返回一個指向實際元素的指針,或者是分配些內存而後拷貝一份。不論哪一種方式,返回的原始指針在相應的Release調用以前都保證有效(這意味着,若是數據沒被拷貝,實際的數組對象將會受到牽制,不能從新成爲整理堆空間的一部分)。你必須釋放(Release)每一個你經過Get獲得的數組。同時,若是Get調用失敗,你必須確保你的代碼在以後不會去嘗試調用Release來釋放一個空指針(NULL pointer)。

你能夠用一個非空指針做爲isCopy參數的值來決定數據是否會被拷貝。這至關有用。

Release類的函數接收一個mode參數,這個參數的值可選的有下面三種。而運行時具體執行的操做取決於它返回的指針是指向真實數據仍是拷貝出來的那份。

  • 0
    • 真實的:實際數組對象不受到牽制
    • 拷貝的:數據將會複製回去,備份空間將會被釋放。
  • JNI_COMMIT
    • 真實的:不作任何操做
    • 拷貝的:數據將會複製回去,備份空間將不會被釋放
  • JNI_ABORT
    • 真實的:實際數組對象不受到牽制.以前的寫入不會被取消。
    • 拷貝的:備份空間將會被釋放;裏面全部的變動都會丟失。

檢查isCopy標識的一個緣由是對一個數組作出變動後確認你是否須要傳入JNI_COMMIT來調用Release函數。若是你交替地執行變動和讀取數組內容的代碼,你也許能夠跳過無操做(no-op)的JNI_COMMIT。檢查這個標識的另外一個可能的緣由是使用JNI_ABORT能夠更高效。例如,你也許想獲得一個數組,適當地修改它,傳入部分到其餘函數中,而後丟掉這些修改。若是你知道JNI是爲你作了一份新的拷貝,就沒有必要再建立另外一份「可編輯的(editable)」的拷貝了。若是JNI傳給你的是原始數組,這時你就須要建立一份你本身的拷貝了。

另外一個常見的錯誤(在示例代碼中出現過)是認爲當isCopy是false時你就能夠不調用Release。其實是沒有這種狀況的。若是沒有分配備份空間,那麼初始的內存空間會受到牽制,位置不能被垃圾回收器移動。

另外注意JNI_COMMIT標識沒有釋放數組,你最終須要使用一個不一樣的標識再次調用Release。

區間數組

當你想作的只是拷出或者拷進數據時,能夠選擇調用像GetArrayElements和GetStringChars這類很是有用的函數。想一想下面:

jbyte* data = env->GetByteArrayElements(array, NULL);
if (data != NULL) { memcpy(buffer, data, len); env->ReleaseByteArrayElements(array, data, JNI_ABORT); } 

這裏獲取到了數組,從當中拷貝出開頭的len個字節元素,而後釋放這個數組。根據代碼的實現,Get函數將會牽制或者拷貝數組的內容。上面的代碼拷貝了數據(爲了可能的第二次),而後調用Release;這當中JNI_ABORT確保不存在第三份拷貝了。

另外一種更簡單的實現方式:

env->GetByteArrayRegion(array, 0, len, buffer); 

這種方式有幾個優勢:

  • 只須要調用一個JNI函數而是否是兩個,減小了開銷。
  • 不須要指針或者額外的拷貝數據。
  • 減小了開發人員犯錯的風險-在某些失敗以後忘記調用Release不存在風險。

相似地,你能使用SetArrayRegion函數拷貝數據到數組,使用GetStringRegion或者GetStringUTFRegion從String中拷貝字符。

異常

當異常發生時你必定不能調用大部分的JNI函數。你的代碼收到異常(經過函數的返回值,ExceptionCheck,或者ExceptionOccurred),而後返回,或者清除異常,處理掉。

當異常發生時你被容許調用的JNI函數有:

  • DeleteGlobalRef
  • DeleteLocalRef
  • DeleteWeakGlobalRef
  • ExceptionCheck
  • ExceptionClear
  • ExceptionDescribe
  • ExceptionOccurred
  • MonitorExit
  • PopLocalFrame
  • PushLocalFrame
  • ReleaseArrayElements
  • ReleasePrimitiveArrayCritical
  • ReleaseStringChars
  • ReleaseStringCritical
  • ReleaseStringUTFChars

許多JNI調用可以拋出異常,但一般提供一種簡單的方式來檢查失敗。例如,若是NewString返回一個非空值,你不須要檢查異常。然而,若是你調用一個方法(使用一個像CalllObjectMethod的函數),你必須一直檢查異常,由於當一個異常拋出時它的返回值將不會是有效的。

注意中斷代碼拋出的異常不會展開本地調用堆棧信息,Android也還不支持C++異常。JNI Throw和ThrowNew指令僅僅是在當前線程中放入一個異常指針。從本地代碼返回到託管代碼時,異常將會被注意到,獲得適當的處理。

本地代碼可以經過調用ExceptionCheck或者ExceptionOccurred捕獲到異常,而後使用ExceptionClear清除掉。一般,拋棄異常而不處理會致使些問題。

沒有內建的函數來處理Throwable對象自身,所以若是你想獲得異常字符串,你須要找出Throwable Class,而後查找到getMessage "()Ljava/lang/String;"的方法ID,調用它,若是結果非空,使用GetStringUTFChars,獲得的結果你能夠傳到printf(3) 或者其它相同功能的函數輸出。

擴展檢查

JNI的錯誤檢查不多。錯誤發生時一般會致使崩潰。Android也提供了一種模式,叫作CheckJNI,這當中JavaVM和JNIEnv函數表指針被換成了函數表,它在調用標準實現以前執行了一系列擴展檢查的。

額外的檢查包括:

  • 數組:試圖分配一個長度爲負的數組。
  • 壞指針:傳入一個不完整jarray/jclass/jobject/jstring對象到JNI函數,或者調用JNI函數時使用空指針傳入到一個不能爲空的參數中去。
  • 類名:傳入了除「java/lang/String」以外的類名到JNI函數。
  • 關鍵調用:在一個「關鍵的(critical)」get和它對應的release之間作出JNI調用。
  • 直接的ByteBuffers:傳入不正確的參數到NewDirectByteBuffer。
  • 異常:當一個異常發生時調用了JNI函數。
  • JNIEnvs:在錯誤的線程中使用一個JNIEnv
  • jfieldIDs:使用一個空jfieldID,或者使用jfieldID設置了一個錯誤類型的值到字段(好比說,試圖將一個StringBuilder賦給String類型的域),或者使用一個靜態字段下的jfieldID設置到一個實例的字段(instance field)反之亦然,或者使用的一個類的jfieldID卻來自另外一個類的實例。
  • jmethodIDs:當調用Call*Method函數時時使用了類型錯誤的jmethodID:不正確的返回值,靜態/非靜態的不匹配,this的類型錯誤(對於非靜態調用)或者錯誤的類(對於靜態類調用)。
  • 引用:在類型錯誤的引用上使用了DeleteGlobalRef/DeleteLocalRef。
  • 釋放模式:調用release使用一個不正確的釋放模式(其它非 0,JNI_ABORT,JNI_COMMIT的值)。
  • 類型安全:從你的本地代碼中返回了一個不兼容的類型(好比說,從一個聲明返回String的方法卻返回了StringBuilder)。
  • UTF-8:傳入一個無效的變形UTF-8字節序列到JNI調用。

(方法和域的可訪問性仍然沒有檢查:訪問限制對於本地代碼並不適用。)

有幾種方法去啓用CheckJNI。

若是你正在使用模擬器,CheckJNI默認是打開的。

若是你有一臺root過的設備,你可使用下面的命令序列來重啓運行時(runtime),啓用CheckJNI。

adb shell stop
adb shell setprop dalvik.vm.checkjni true adb shell start 

隨便哪種,當運行時(runtime)啓動時你將會在你的日誌輸出中見到以下的字符:

D AndroidRuntime: CheckJNI is ON

若是你有一臺常規的設備,你可使用下面的命令:

adb shell setprop debug.checkjni 1 

這將不會影響已經在運行的app,可是從那之後啓動的任何app都將打開CheckJNI(改變屬性爲其它值或者只是重啓都將會再次關閉CheckJNI)。這種狀況下,你將會在下一次app啓動時,在日誌輸出中看到以下字符:

D Late-enabling CheckJNI

本地庫

你可使用標準的System.loadLibrary方法來從共享庫中加載本地代碼。在你的本地代碼中較好的作法是:

  • 在一個靜態類初始化時調用System.loadLibrary(見以前的一個例子中,當中就使用了nativeClassInit)。參數是「未加修飾(undecorated)」的庫名稱,所以要加載「libfubar.so」,你須要傳入「fubar」。
  • 提供一個本地函數:jint JNI_OnLoad(JavaVM vm, void reserved)
  • 在JNI_OnLoad中,註冊全部你的本地方法。你應該聲明方法爲「靜態的(static)」所以名稱不會佔據設備上符號表的空間。

JNI_OnLoad函數在C++中的寫法以下:

jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } // 使用env->FindClass獲得jclass // 使用env->RegisterNatives註冊本地方法 return JNI_VERSION_1_6; } 

你也可使用共享庫的全路徑來調用System.load。對於Android app,你也許會發現從context對象中獲得應用私有數據存儲的全路徑是很是有用的。

上面是推薦的方式,但不是僅有的實現方式。顯式註冊不是必須的,提供一個JNI_OnLoad函數也不是必須的。你可使用基於特殊命名的「發現(discovery)」模式來註冊本地方法(更多細節見:JNI spec),雖然這並不可取。由於若是一個方法的簽名錯誤,在這個方法實際第一次被調用以前你是不會知道的。

關於JNI_OnLoad另外一點注意的是:任何你在JNI_OnLoad中對FindClass的調用都發生在用做加載共享庫的類加載器的上下文(context)中。通常FindClass使用與「調用棧」頂部方法相關的加載器,若是當中沒有加載器(由於線程剛剛鏈接)則使用「系統(system)」類加載器。這就使得JNI_OnLoad成爲一個查尋及緩存類引用很便利的地方。

64位機問題

Android當前設計爲運行在32位的平臺上。理論上它也可以構建爲64位的系統,但那不是如今的目標。當與本地代碼交互時,在大多數狀況下這不是你須要擔憂的,可是若是你打算存儲指針變量到對象的整型字段(integer field)這樣的本地結構中,這就變得很是重要了。爲了支持使用64位指針的架構,你須要使用long類型而不是int類型的字段來存儲你的本地指針

不支持的特性/向後兼容性

除了下面的例外,支持全部的JNI 1.6特性:

  • DefineClass沒有實現。Android不使用Java字節碼或者class文件,所以傳入二進制class數據將不會有效。

對Android之前老版本的向後兼容性,你須要注意:

  • 本地函數的動態查找 在Android 2.0(Eclair)以前,在搜索方法名稱時,字符「$」不會轉換爲對應的「_00024」。要使它正常工做須要使用顯式註冊方式或者將本地方法的聲明移出內部類。
  • 分離線程 在Android 2.0(Eclair)以前,使用pthread_key_create析構函數來避免「退出前線程必須分離」檢查是不可行的(運行時(runtime)也使用了一個pthread key析構函數,所以這是一場看誰先被調用的競賽)。
  • 全局弱引用 在Android 2.0(Eclair)以前,全局弱引用沒有被實現。若是試圖使用它們,老版本將徹底不兼容。你可使用Android平臺版本號常量來測試系統的支持性。 在Android 4.0 (Ice Cream Sandwich)以前,全局弱引用只能傳給NewLocalRef, NewGlobalRef, 以及DeleteWeakGlobalRef(強烈建議開發者在使用全局弱引用以前都爲它們建立強引用hard reference,因此這不該該在全部限制當中)。 從Android 4.0 (Ice Cream Sandwich)起,全局弱引用可以像其它任何JNI引用同樣使用了。
  • 局部引用 在Android 4.0 (Ice Cream Sandwich)以前,局部引用其實是直接指針。Ice Cream Sandwich爲了更好地支持垃圾回收添加了間接指針,但這並不意味着不少JNI bug在老版本上不存在。更多細節見JNI Local Reference Changes in ICS
  • 使用GetObjectRefType得到引用類型 在Android 4.0 (Ice Cream Sandwich)以前,使用直接指針(見上面)的後果就是正確地實現GetObjectRefType是不可能的。咱們可使用依次檢測全局弱引用表,參數,局部表,全局表的方式來代替。第一次匹配到你的直接指針時,就代表你的引用類型是當前正在檢測的類型。這意味着,例如,若是你在一個全局jclass上使用GetObjectRefType,而這個全局jclass碰巧與做爲靜態本地方法的隱式參數傳入的jclass同樣的,你獲得的結果是JNILocalRefType而不是JNIGlobalRefType。

FAQ: 爲何出現了UnsatisfiedLinkError?

當使用本地代碼開發時常常會見到像下面的錯誤:

java.lang.UnsatisfiedLinkError: Library foo not found

有時候這表示和它提示的同樣---未找到庫。但有些時候庫確實存在但不能被dlopen(3)找開,更多的失敗信息能夠參見異常詳細說明。

你遇到「library not found」異常的常見緣由可能有這些:

  • 庫文件不存在或者不能被app訪問到。使用adb shell ls -l 檢查它的存在性和權限。
  • 庫文件不是用NDK構建的。這就致使設備上並不存在它所依賴的函數或者庫。

另外一種UnsatisfiedLinkError錯誤像下面這樣:

java.lang.UnsatisfiedLinkError: myfunc
        at Foo.myfunc(Native Method)
        at Foo.main(Foo.java:10) 

在日誌中,你會發現:

W/dalvikvm(  880): No implementation found for native LFoo;.myfunc ()V 

這意味着運行時嘗試匹配一個方法可是沒有成功,這種狀況常見的緣由有:

  • 庫文件沒有獲得加載。檢查日誌輸出中關於庫文件加載的信息。
  • 因爲名稱或者簽名錯誤,方法不能匹配成功。這一般是因爲:
    • 對於方法的懶查尋,使用 extern "C"和對應的可見性(JNIEXPORT)來聲明C++函數沒有成功。注意Ice Cream Sandwich以前的版本,JNIEXPORT宏是不正確的,所以對新版本的GCC使用舊的jni.h頭文件將不會有效。你可使用arm-eabi-nm查看它們出如今庫文件裏的符號。若是它們看上去比較凌亂(像_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass這樣而不是Java_Foo_myfunc),或者符號類型是小寫的「t」而不是一個大寫的「T」,這時你就須要調整聲明瞭。
    • 對於顯式註冊,在進行方法簽名時可能犯了些小錯誤。確保你傳入到註冊函數的簽名可以徹底匹配上日誌文件裏提示的。記住「B」是byte,「Z」是boolean。在簽名中類名組件是以「L」開頭的,以「;」結束的,使用「/」來分隔包名/類名,使用「$」符來分隔內部類名稱(好比說,Ljava/util/Map$Entry;)。

使用javah來自動生成JNI頭文件也許能幫助你避免這些問題。

FAQ: 爲何FindClass不能找到個人類?

確保類名字符串有正確的格式。JNI類名稱以包名開始,而後使用左斜槓來分隔,好比java/lang/String。若是你正在查找一個數組類,你須要以對應數目的綜括號開頭,使用「L」和「;」將類名兩頭包起來,因此一個一維字符串數組應該寫成[Ljava/lang/String;。

若是類名稱看上去正確,你可能運行時遇到了類加載器的問題。FindClass想在與你代碼相關的類加載器中開始查找指定的類。檢查調用堆棧,可能看起像:

Foo.myfunc(Native Method)
Foo.main(Foo.java:10) dalvik.syste
相關文章
相關標籤/搜索