編寫(xiě):pedant - 原文:http://developer.android.com/training/articles/perf-jni.html
JNI全稱Java Native Interface。它為托管代碼(使用Java編程語(yǔ)言編寫(xiě))與本地代碼(使用C/C++編寫(xiě))提供了一種交互方式。它是與廠商無(wú)關(guān)的(vendor-neutral),支持從動(dòng)態(tài)共享庫(kù)中加載代碼,雖然這樣會(huì)稍顯麻煩,但有時(shí)這是相當(dāng)有效的。
如果你對(duì)JNI還不是太熟悉,可以先通讀Java Native Interface Specification這篇文章來(lái)對(duì)JNI如何工作以及哪些特性可用有個(gè)大致的印象。這種接口的一些方面不能立即一讀就顯而易見(jiàn),所以你會(huì)發(fā)現(xiàn)接下來(lái)的幾個(gè)章節(jié)很有用處。
JNI定義了兩種關(guān)鍵數(shù)據(jù)結(jié)構(gòu),“JavaVM”和“JNIEnv”。它們本質(zhì)上都是指向函數(shù)表指針的指針(在C++版本中,它們被定義為類,該類包含一個(gè)指向函數(shù)表的指針,以及一系列可以通過(guò)這個(gè)函數(shù)表間接地訪問(wèn)對(duì)應(yīng)的JNI函數(shù)的成員函數(shù))。JavaVM提供“調(diào)用接口(invocation interface)”函數(shù), 允許你創(chuàng)建和銷毀一個(gè)JavaVM。理論上你可以在一個(gè)進(jìn)程中擁有多個(gè)JavaVM對(duì)象,但安卓只允許一個(gè)。
JNIEnv提供了大部分JNI功能。你定義的所有本地函數(shù)都會(huì)接收J(rèn)NIEnv作為第一個(gè)參數(shù)。
JNIEnv是用作線程局部存儲(chǔ)。因此,你不能在線程間共享一個(gè)JNIEnv變量。如果在一段代碼中沒(méi)有其它辦法獲得它的JNIEnv,你可以共享JavaVM對(duì)象,使用GetEnv來(lái)取得該線程下的JNIEnv(如果該線程有一個(gè)JavaVM的話;見(jiàn)下面的AttachCurrentThread)。
JNIEnv和JavaVM的在C聲明是不同于在C++的聲明。頭文件“jni.h”根據(jù)它是以C還是以C++模式包含來(lái)提供不同的類型定義(typedefs)。因此,不建議把JNIEnv參數(shù)放到可能被兩種語(yǔ)言引入的頭文件中(換一句話說(shuō):如果你的頭文件需要#ifdef __cplusplus,你可能不得不在任何涉及到JNIEnv的內(nèi)容處都要做些額外的工作)。
所有的線程都是Linux線程,由內(nèi)核統(tǒng)一調(diào)度。它們通常從托管代碼中啟動(dòng)(使用Thread.start),但它們也能夠在其他任何地方創(chuàng)建,然后連接(attach)到JavaVM。例如,一個(gè)用pthread_create啟動(dòng)的線程能夠使用JNI AttachCurrentThread 或 AttachCurrentThreadAsDaemon函數(shù)連接到JavaVM。在一個(gè)線程成功連接(attach)之前,它沒(méi)有JNIEnv,不能夠調(diào)用JNI函數(shù)。
連接一個(gè)本地環(huán)境創(chuàng)建的線程會(huì)觸發(fā)構(gòu)造一個(gè)java.lang.Thread對(duì)象,然后其被添加到主線程群組(main ThreadGroup),以讓調(diào)試器可以探測(cè)到。對(duì)一個(gè)已經(jīng)連接的線程使用AttachCurrentThread不做任何操作(no-op)。
安卓不能中止正在執(zhí)行本地代碼的線程。如果正在進(jìn)行垃圾回收,或者調(diào)試器已發(fā)出了中止請(qǐng)求,安卓會(huì)在下一次調(diào)用JNI函數(shù)的時(shí)候中止線程。
連接過(guò)的(attached)線程在它們退出之前必須通過(guò)JNI調(diào)用DetachCurrentThread。如果你覺(jué)得直接這樣編寫(xiě)不太優(yōu)雅,在安卓2.0(Eclair)及以上, 你可以使用pthread_key_create來(lái)定義一個(gè)析構(gòu)函數(shù),它將會(huì)在線程退出時(shí)被調(diào)用,你可以在那兒調(diào)用DetachCurrentThread (使用生成的key與pthread_setspecific將JNIEnv存儲(chǔ)到線程局部空間內(nèi);這樣JNIEnv能夠作為參數(shù)傳入到析構(gòu)函數(shù)當(dāng)中去)。
如果你想在本地代碼中訪問(wèn)一個(gè)對(duì)象的字段(field),你可以像下面這樣做:
類似地,要調(diào)用一個(gè)方法,你首先得獲得一個(gè)類對(duì)象的引用,然后是方法ID(method ID)。這些ID通常是指向運(yùn)行時(shí)內(nèi)部數(shù)據(jù)結(jié)構(gòu)。查找到它們需要些字符串比較,但一旦你實(shí)際去執(zhí)行它們獲得字段或者做方法調(diào)用是非常快的。
如果性能是你看重的,那么一旦查找出這些值之后在你的本地代碼中緩存這些結(jié)果是非常有用的。因?yàn)槊總€(gè)進(jìn)程當(dāng)中的JavaVM是存在限制的,存儲(chǔ)這些數(shù)據(jù)到本地靜態(tài)數(shù)據(jù)結(jié)構(gòu)中是非常合理的。
類引用(class reference),字段ID(field ID)以及方法ID(method ID)在類被卸載前都是有效的。如果與一個(gè)類加載器(ClassLoader)相關(guān)的所有類都能夠被垃圾回收,但是這種情況在安卓上是罕見(jiàn)甚至不可能出現(xiàn),只有這時(shí)類才被卸載。注意雖然jclass是一個(gè)類引用,但是必須要調(diào)用NewGlobalRef保護(hù)起來(lái)(見(jiàn)下個(gè)章節(jié))。
當(dāng)一個(gè)類被加載時(shí)如果你想緩存些ID,而后當(dāng)這個(gè)類被卸載后再次載入時(shí)能夠自動(dòng)地更新這些緩存ID,正確做法是在對(duì)應(yīng)的類中添加一段像下面的代碼來(lái)初始化這些ID:
/*
* 我們?cè)谝粋€(gè)類初始化時(shí)調(diào)用本地方法來(lái)緩存一些字段的偏移信息
* 這個(gè)本地方法查找并緩存你感興趣的class/field/method ID
* 失敗時(shí)拋出異常
*/
private static native void nativeInit();
static {
nativeInit();
}
在你的C/C++代碼中創(chuàng)建一個(gè)nativeClassInit方法以完成ID查找的工作。當(dāng)這個(gè)類被初始化時(shí)這段代碼將會(huì)執(zhí)行一次。當(dāng)這個(gè)類被卸載后而后再次載入時(shí),這段代碼將會(huì)再次執(zhí)行。
每個(gè)傳入本地方法的參數(shù),以及大部分JNI函數(shù)返回的每個(gè)對(duì)象都是“局部引用”。這意味著它只在當(dāng)前線程的當(dāng)前方法執(zhí)行期間有效。即使這個(gè)對(duì)象本身在本地方法返回之后仍然存在,這個(gè)引用也是無(wú)效的。
這同樣適用于所有jobject的子類,包括jclass,jstring,以及jarray(當(dāng)JNI擴(kuò)展檢查是打開(kāi)的時(shí)候,運(yùn)行時(shí)會(huì)警告你對(duì)大部分對(duì)象引用的誤用)。
如果你想持有一個(gè)引用更長(zhǎng)的時(shí)間,你就必須使用一個(gè)全局(“global”)引用了。NewGlobalRef函數(shù)以一個(gè)局部引用作為參數(shù)并且返回一個(gè)全局引用。全局引用能夠保證在你調(diào)用DeleteGlobalRef前都是有效的。
這種模式通常被用在緩存一個(gè)從FindClass返回的jclass對(duì)象的時(shí)候,例如:
jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
所有的JNI方法都接收局部引用和全局引用作為參數(shù)。相同對(duì)象的引用卻可能具有不同的值。例如,用相同對(duì)象連續(xù)地調(diào)用NewGlobalRef得到返回值可能是不同的。為了檢查兩個(gè)引用是否指向的是同一個(gè)對(duì)象,你必須使用IsSameObject函數(shù)。絕不要在本地代碼中用==符號(hào)來(lái)比較兩個(gè)引用。
得出的結(jié)論就是你絕不要在本地代碼中假定對(duì)象的引用是常量或者是唯一的。代表一個(gè)對(duì)象的32位值從方法的一次調(diào)用到下一次調(diào)用可能有不同的值。在連續(xù)的調(diào)用過(guò)程中兩個(gè)不同的對(duì)象卻可能擁有相同的32位值。不要使用jobject的值作為key.
開(kāi)發(fā)者需要“不過(guò)度分配”局部引用。在實(shí)際操作中這意味著如果你正在創(chuàng)建大量的局部引用,或許是通過(guò)對(duì)象數(shù)組,你應(yīng)該使用DeleteLocalRef手動(dòng)地釋放它們,而不是寄希望JNI來(lái)為你做這些。實(shí)現(xiàn)上只預(yù)留了16個(gè)局部引用的空間,所以如果你需要更多,要么你刪掉以前的,要么使用EnsureLocalCapacity/PushLocalFrame來(lái)預(yù)留更多。
注意jfieldID和jmethodID是映射類型(opaque types),不是對(duì)象引用,不應(yīng)該被傳入到NewGlobalRef。原始數(shù)據(jù)指針,像GetStringUTFChars和GetByteArrayElements的返回值,也都不是對(duì)象(它們能夠在線程間傳遞,并且在調(diào)用對(duì)應(yīng)的Release函數(shù)之前都是有效的)。
還有一種不常見(jiàn)的情況值得一提,如果你使用AttachCurrentThread連接(attach)了本地進(jìn)程,正在運(yùn)行的代碼在線程分離(detach)之前決不會(huì)自動(dòng)釋放局部引用。你創(chuàng)建的任何局部引用必須手動(dòng)刪除。通常,任何在循環(huán)中創(chuàng)建局部引用的本地代碼可能都需要做一些手動(dòng)刪除。
Java編程語(yǔ)言使用UTF-16格式。為了便利,JNI也提供了支持變形UTF-8(Modified UTF-8)的方法。這種變形編碼對(duì)于C代碼是非常有用的,因?yàn)樗鼘u0000編碼成0xc0 0x80,而不是0x00。最愜意的事情是你能在具有C風(fēng)格的以\0結(jié)束的字符串上計(jì)數(shù),同時(shí)兼容標(biāo)準(zhǔn)的libc字符串函數(shù)。不好的一面是你不能傳入隨意的UTF-8數(shù)據(jù)到JNI函數(shù)而還指望它正常工作。
如果可能的話,直接操作UTF-16字符串通常更快些。安卓當(dāng)前在調(diào)用GetStringChars時(shí)不需要拷貝,而GetStringUTFChars需要一次分配并且轉(zhuǎn)換為UTF-8格式。注意UTF-16字符串不是以零終止字符串,\u0000是被允許的,所以你需要像對(duì)jchar指針一樣地處理字符串的長(zhǎng)度。
不要忘記Release你Get的字符串。這些字符串函數(shù)返回jchar或者jbyte,都是指向基本數(shù)據(jù)類型的C格式的指針而不是局部引用。它們?cè)赗elease調(diào)用之前都保證有效,這意味著當(dāng)本地方法返回時(shí)它們并不主動(dòng)釋放。
傳入NewStringUTF函數(shù)的數(shù)據(jù)必須是變形UTF-8格式。一種常見(jiàn)的錯(cuò)誤情況是,從文件或者網(wǎng)絡(luò)流中讀取出的字符數(shù)據(jù),沒(méi)有過(guò)濾直接使用NewStringUTF處理。除非你確定數(shù)據(jù)是7位的ASCII格式,否則你需要剔除超出7位ASCII編碼范圍(high-ASCII)的字符或者將它們轉(zhuǎn)換為對(duì)應(yīng)的變形UTF-8格式。如果你沒(méi)那樣做,UTF-16的轉(zhuǎn)換結(jié)果可能不會(huì)是你想要的結(jié)果。JNI擴(kuò)展檢查將會(huì)掃描字符串,然后警告你那些無(wú)效的數(shù)據(jù),但是它們將不會(huì)發(fā)現(xiàn)所有潛在的風(fēng)險(xiǎn)。
JNI提供了一系列函數(shù)來(lái)訪問(wèn)數(shù)組對(duì)象中的內(nèi)容。對(duì)象數(shù)組的訪問(wèn)只能一次一條,但如果原生類型數(shù)組以C方式聲明,則能夠直接進(jìn)行讀寫(xiě)。
為了讓接口更有效率而不受VM實(shí)現(xiàn)的制約,Get
你可以用一個(gè)非空指針作為isCopy參數(shù)的值來(lái)決定數(shù)據(jù)是否會(huì)被拷貝。這相當(dāng)有用。
Release類的函數(shù)接收一個(gè)mode參數(shù),這個(gè)參數(shù)的值可選的有下面三種。而運(yùn)行時(shí)具體執(zhí)行的操作取決于它返回的指針是指向真實(shí)數(shù)據(jù)還是拷貝出來(lái)的那份。
檢查isCopy標(biāo)識(shí)的一個(gè)原因是對(duì)一個(gè)數(shù)組做出變更后確認(rèn)你是否需要傳入JNI_COMMIT來(lái)調(diào)用Release函數(shù)。如果你交替地執(zhí)行變更和讀取數(shù)組內(nèi)容的代碼,你也許可以跳過(guò)無(wú)操作(no-op)的JNI_COMMIT。檢查這個(gè)標(biāo)識(shí)的另一個(gè)可能的原因是使用JNI_ABORT可以更高效。例如,你也許想得到一個(gè)數(shù)組,適當(dāng)?shù)匦薷乃瑐魅氩糠值狡渌瘮?shù)中,然后丟掉這些修改。如果你知道JNI是為你做了一份新的拷貝,就沒(méi)有必要再創(chuàng)建另一份“可編輯的(editable)”的拷貝了。如果JNI傳給你的是原始數(shù)組,這時(shí)你就需要?jiǎng)?chuàng)建一份你自己的拷貝了。
另一個(gè)常見(jiàn)的錯(cuò)誤(在示例代碼中出現(xiàn)過(guò))是認(rèn)為當(dāng)isCopy是false時(shí)你就可以不調(diào)用Release。實(shí)際上是沒(méi)有這種情況的。如果沒(méi)有分配備份空間,那么初始的內(nèi)存空間會(huì)受到牽制,位置不能被垃圾回收器移動(dòng)。
另外注意JNI_COMMIT標(biāo)識(shí)沒(méi)有釋放數(shù)組,你最終需要使用一個(gè)不同的標(biāo)識(shí)再次調(diào)用Release。
當(dāng)你想做的只是拷出或者拷進(jìn)數(shù)據(jù)時(shí),可以選擇調(diào)用像Get
jbyte* data = env->GetByteArrayElements(array, NULL);
if (data != NULL) {
memcpy(buffer, data, len);
env->ReleaseByteArrayElements(array, data, JNI_ABORT);
}
這里獲取到了數(shù)組,從當(dāng)中拷貝出開(kāi)頭的len個(gè)字節(jié)元素,然后釋放這個(gè)數(shù)組。根據(jù)代碼的實(shí)現(xiàn),Get函數(shù)將會(huì)牽制或者拷貝數(shù)組的內(nèi)容。上面的代碼拷貝了數(shù)據(jù)(為了可能的第二次),然后調(diào)用Release;這當(dāng)中JNI_ABORT確保不存在第三份拷貝了。
另一種更簡(jiǎn)單的實(shí)現(xiàn)方式:
env->GetByteArrayRegion(array, 0, len, buffer);
這種方式有幾個(gè)優(yōu)點(diǎn):
類似地,你能使用Set
當(dāng)異常發(fā)生時(shí)你一定不能調(diào)用大部分的JNI函數(shù)。你的代碼收到異常(通過(guò)函數(shù)的返回值,ExceptionCheck,或者ExceptionOccurred),然后返回,或者清除異常,處理掉。
當(dāng)異常發(fā)生時(shí)你被允許調(diào)用的JNI函數(shù)有:
許多JNI調(diào)用能夠拋出異常,但通常提供一種簡(jiǎn)單的方式來(lái)檢查失敗。例如,如果NewString返回一個(gè)非空值,你不需要檢查異常。然而,如果你調(diào)用一個(gè)方法(使用一個(gè)像CalllObjectMethod的函數(shù)),你必須一直檢查異常,因?yàn)楫?dāng)一個(gè)異常拋出時(shí)它的返回值將不會(huì)是有效的。
注意中斷代碼拋出的異常不會(huì)展開(kāi)本地調(diào)用堆棧信息,Android也還不支持C++異常。JNI Throw和ThrowNew指令僅僅是在當(dāng)前線程中放入一個(gè)異常指針。從本地代碼返回到托管代碼時(shí),異常將會(huì)被注意到,得到適當(dāng)?shù)奶幚怼?/p>
本地代碼能夠通過(guò)調(diào)用ExceptionCheck或者ExceptionOccurred捕獲到異常,然后使用ExceptionClear清除掉。通常,拋棄異常而不處理會(huì)導(dǎo)致些問(wèn)題。
沒(méi)有內(nèi)建的函數(shù)來(lái)處理Throwable對(duì)象自身,因此如果你想得到異常字符串,你需要找出Throwable Class,然后查找到getMessage "()Ljava/lang/String;"的方法ID,調(diào)用它,如果結(jié)果非空,使用GetStringUTFChars,得到的結(jié)果你可以傳到printf(3) 或者其它相同功能的函數(shù)輸出。
JNI的錯(cuò)誤檢查很少。錯(cuò)誤發(fā)生時(shí)通常會(huì)導(dǎo)致崩潰。Android也提供了一種模式,叫做CheckJNI,這當(dāng)中JavaVM和JNIEnv函數(shù)表指針被換成了函數(shù)表,它在調(diào)用標(biāo)準(zhǔn)實(shí)現(xiàn)之前執(zhí)行了一系列擴(kuò)展檢查的。
額外的檢查包括:
(方法和域的可訪問(wèn)性仍然沒(méi)有檢查:訪問(wèn)限制對(duì)于本地代碼并不適用。)
有幾種方法去啟用CheckJNI。
如果你正在使用模擬器,CheckJNI默認(rèn)是打開(kāi)的。
如果你有一臺(tái)root過(guò)的設(shè)備,你可以使用下面的命令序列來(lái)重啟運(yùn)行時(shí)(runtime),啟用CheckJNI。
adb shell stop
adb shell setprop dalvik.vm.checkjni true
adb shell start
隨便哪一種,當(dāng)運(yùn)行時(shí)(runtime)啟動(dòng)時(shí)你將會(huì)在你的日志輸出中見(jiàn)到如下的字符:
D AndroidRuntime: CheckJNI is ON
如果你有一臺(tái)常規(guī)的設(shè)備,你可以使用下面的命令:
adb shell setprop debug.checkjni 1
這將不會(huì)影響已經(jīng)在運(yùn)行的app,但是從那以后啟動(dòng)的任何app都將打開(kāi)CheckJNI(改變屬性為其它值或者只是重啟都將會(huì)再次關(guān)閉CheckJNI)。這種情況下,你將會(huì)在下一次app啟動(dòng)時(shí),在日志輸出中看到如下字符:
D Late-enabling CheckJNI
你可以使用標(biāo)準(zhǔn)的System.loadLibrary方法來(lái)從共享庫(kù)中加載本地代碼。在你的本地代碼中較好的做法是:
JNI_OnLoad函數(shù)在C++中的寫(xiě)法如下:
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得到j(luò)class
// 使用env->RegisterNatives注冊(cè)本地方法
return JNI_VERSION_1_6;
}
你也可以使用共享庫(kù)的全路徑來(lái)調(diào)用System.load。對(duì)于Android app,你也許會(huì)發(fā)現(xiàn)從context對(duì)象中得到應(yīng)用私有數(shù)據(jù)存儲(chǔ)的全路徑是非常有用的。
上面是推薦的方式,但不是僅有的實(shí)現(xiàn)方式。顯式注冊(cè)不是必須的,提供一個(gè)JNI_OnLoad函數(shù)也不是必須的。你可以使用基于特殊命名的“發(fā)現(xiàn)(discovery)”模式來(lái)注冊(cè)本地方法(更多細(xì)節(jié)見(jiàn):JNI spec),雖然這并不可取。因?yàn)槿绻粋€(gè)方法的簽名錯(cuò)誤,在這個(gè)方法實(shí)際第一次被調(diào)用之前你是不會(huì)知道的。
關(guān)于JNI_OnLoad另一點(diǎn)注意的是:任何你在JNI_OnLoad中對(duì)FindClass的調(diào)用都發(fā)生在用作加載共享庫(kù)的類加載器的上下文(context)中。一般FindClass使用與“調(diào)用?!表敳糠椒ㄏ嚓P(guān)的加載器,如果當(dāng)中沒(méi)有加載器(因?yàn)榫€程剛剛連接)則使用“系統(tǒng)(system)”類加載器。這就使得JNI_OnLoad成為一個(gè)查尋及緩存類引用很便利的地方。
Android當(dāng)前設(shè)計(jì)為運(yùn)行在32位的平臺(tái)上。理論上它也能夠構(gòu)建為64位的系統(tǒng),但那不是現(xiàn)在的目標(biāo)。當(dāng)與本地代碼交互時(shí),在大多數(shù)情況下這不是你需要擔(dān)心的,但是如果你打算存儲(chǔ)指針變量到對(duì)象的整型字段(integer field)這樣的本地結(jié)構(gòu)中,這就變得非常重要了。為了支持使用64位指針的架構(gòu),你需要使用long類型而不是int類型的字段來(lái)存儲(chǔ)你的本地指針。
除了下面的例外,支持所有的JNI 1.6特性:
對(duì)Android以前老版本的向后兼容性,你需要注意:
當(dāng)使用本地代碼開(kāi)發(fā)時(shí)經(jīng)常會(huì)見(jiàn)到像下面的錯(cuò)誤:
java.lang.UnsatisfiedLinkError: Library foo not found
有時(shí)候這表示和它提示的一樣---未找到庫(kù)。但有些時(shí)候庫(kù)確實(shí)存在但不能被dlopen(3)找開(kāi),更多的失敗信息可以參見(jiàn)異常詳細(xì)說(shuō)明。
你遇到“l(fā)ibrary not found”異常的常見(jiàn)原因可能有這些:
另一種UnsatisfiedLinkError錯(cuò)誤像下面這樣:
java.lang.UnsatisfiedLinkError: myfunc
at Foo.myfunc(Native Method)
at Foo.main(Foo.java:10)
在日志中,你會(huì)發(fā)現(xiàn):
W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V
這意味著運(yùn)行時(shí)嘗試匹配一個(gè)方法但是沒(méi)有成功,這種情況常見(jiàn)的原因有:
使用javah來(lái)自動(dòng)生成JNI頭文件也許能幫助你避免這些問(wèn)題。
確保類名字符串有正確的格式。JNI類名稱以包名開(kāi)始,然后使用左斜杠來(lái)分隔,比如java/lang/String。如果你正在查找一個(gè)數(shù)組類,你需要以對(duì)應(yīng)數(shù)目的綜括號(hào)開(kāi)頭,使用“L”和“;”將類名兩頭包起來(lái),所以一個(gè)一維字符串?dāng)?shù)組應(yīng)該寫(xiě)成[Ljava/lang/String;。
如果類名稱看上去正確,你可能運(yùn)行時(shí)遇到了類加載器的問(wèn)題。FindClass想在與你代碼相關(guān)的類加載器中開(kāi)始查找指定的類。檢查調(diào)用堆棧,可能看起像:
Foo.myfunc(Native Method)
Foo.main(Foo.java:10)
dalvik.system.NativeStart.main(Native Method)
最頂層的方法是Foo.myfunc。FindClass找到與類Foo相關(guān)的ClassLoader對(duì)象然后使用它。
這通常正是你所想的。如果你創(chuàng)建了自己的線程那么就會(huì)遇到麻煩(也許是調(diào)用了pthread_create然后使用AttachCurrentThread進(jìn)行了連接)。現(xiàn)在跟蹤堆??赡芟裣旅孢@樣:
dalvik.system.NativeStart.run(Native Method)
最頂層的方法是NativeStart.run,它不是你應(yīng)用內(nèi)的方法。如果你從這個(gè)線程中調(diào)用FindClass,JavaVM將會(huì)啟動(dòng)“系統(tǒng)(system)”的而不是與你應(yīng)用相關(guān)的加載器,因此試圖查找應(yīng)用內(nèi)定義的類都將會(huì)失敗。
下面有幾種方法可以解決這個(gè)問(wèn)題:
也許你會(huì)遇到這樣一種情況,想從你的托管代碼或者本地代碼訪問(wèn)一大塊原始數(shù)據(jù)的緩沖區(qū)。常見(jiàn)例子包括對(duì)bitmap或者聲音文件的處理。這里有兩種基本實(shí)現(xiàn)方式。
你可以將數(shù)據(jù)存儲(chǔ)到byte[]。這允許你從托管代碼中快速地訪問(wèn)。然而,在本地代碼端不能保證你不去拷貝一份就直接能夠訪問(wèn)數(shù)據(jù)。在某些實(shí)現(xiàn)中,GetByteArrayElements和GetPrimitiveArrayCritical將會(huì)返回指向在維護(hù)堆中的原始數(shù)據(jù)的真實(shí)指針,但是在另外一些實(shí)現(xiàn)中將在本地堆空間分配一塊緩沖區(qū)然后拷貝數(shù)據(jù)過(guò)去。
還有一種選擇是將數(shù)據(jù)存儲(chǔ)在一塊直接字節(jié)緩沖區(qū)(direct byte buffer),可以使用java.nio.ByteBuffer.allocateDirect或者NewDirectByteBuffer JNI函數(shù)創(chuàng)建buffer。不像常規(guī)的byte緩沖區(qū),它的存儲(chǔ)空間將不會(huì)分配在程序維護(hù)的堆空間上,總是可以從本地代碼直接訪問(wèn)(使用GetDirectBufferAddress得到地址)。依賴于直接字節(jié)緩沖區(qū)訪問(wèn)的實(shí)現(xiàn)方式,從托管代碼訪問(wèn)原始數(shù)據(jù)將會(huì)非常慢。
選擇使用哪種方式取決于兩個(gè)方面:
1.大部分的數(shù)據(jù)訪問(wèn)是在Java代碼還是C/C++代碼中發(fā)生?
2.如果數(shù)據(jù)最終被傳到系統(tǒng)API,那它必須是怎樣的形式(例如,如果數(shù)據(jù)最終被傳到一個(gè)使用byte[]作為參數(shù)的函數(shù),在直接的ByteBuffer中處理或許是不明智的)?
如果通過(guò)上面兩種情況仍然不能明確區(qū)分的,就使用直接字節(jié)緩沖區(qū)(direct byte buffer)形式。它們的支持是直接構(gòu)建到JNI中的,在未來(lái)的版本中性能可能會(huì)得到提升。