鍍金池/ 教程/ Android/ JNI Tips
檢測(cè)常用的手勢(shì)
優(yōu)化layout的層級(jí)
用戶輸入
管理應(yīng)用的內(nèi)存
聯(lián)系人信息
開(kāi)發(fā)輔助程序
Android多媒體
添加語(yǔ)音功能
顯示位置地址
提供向下與橫向?qū)Ш?/span>
支持游戲控制器
訪問(wèn)可穿戴數(shù)據(jù)層
處理多點(diǎn)觸控手勢(shì)
全屏沉浸式應(yīng)用
為多線程創(chuàng)建管理器
數(shù)據(jù)保存
Intent的發(fā)送
更新Notification
優(yōu)化下載以高效地訪問(wèn)網(wǎng)絡(luò)
打印
打包可穿戴應(yīng)用
接收從其他App傳送來(lái)的數(shù)據(jù)
發(fā)送與接收消息
建立靈活動(dòng)態(tài)的UI
處理鍵盤(pán)輸入
Building a Work Policy Controller
建立測(cè)試環(huán)境
創(chuàng)建表盤(pán)
分享文件
顯示Notification進(jìn)度
實(shí)現(xiàn)自適應(yīng)UI流(Flows)
使用設(shè)備管理策略增強(qiáng)安全性
使用能感知版本的組件
執(zhí)行網(wǎng)絡(luò)操作
建立文件分享
添加移動(dòng)
更新你的Security Provider來(lái)對(duì)抗SSL漏洞利用
支持鍵盤(pán)導(dǎo)航
創(chuàng)建和監(jiān)視地理圍欄
發(fā)送并同步數(shù)據(jù)
使用BigView樣式
無(wú)線連接設(shè)備
提供向上導(dǎo)航與歷史導(dǎo)航
最小化定期更新造成的影響
實(shí)現(xiàn)向下的導(dǎo)航
支持不同的屏幕大小
Android 可穿戴應(yīng)用
添加動(dòng)畫(huà)
顯示聯(lián)系人頭像
使用OpenGL ES顯示圖像
處理輸入法可見(jiàn)性
分享文件
保持設(shè)備喚醒
淡化系統(tǒng)Bar
使用NFC分享文件
保存到Preference
Android聯(lián)系人信息與位置信息
創(chuàng)建標(biāo)準(zhǔn)的網(wǎng)絡(luò)請(qǐng)求
使用Drawables
管理Bitmap的內(nèi)存使用
管理Activity的生命周期
按需加載視圖
傳輸資源
為可穿戴設(shè)備創(chuàng)建自定義UI
在一個(gè)線程中執(zhí)行一段特定的代碼
性能優(yōu)化
隱藏導(dǎo)航欄
創(chuàng)建目錄瀏覽器
為多種大小的屏幕進(jìn)行規(guī)劃
View間漸變
使用觸摸手勢(shì)
高效加載大圖
使用CursorLoader在后臺(tái)加載數(shù)據(jù)
創(chuàng)建抽屜式導(dǎo)航(navigation drawer)
管理音頻焦點(diǎn)
創(chuàng)建后臺(tái)服務(wù)
創(chuàng)建功能測(cè)試
創(chuàng)建使用Material Design的應(yīng)用
停止與重啟Activity
添加一個(gè)簡(jiǎn)便的分享功能
啟動(dòng)Activity時(shí)保留導(dǎo)航
TV應(yīng)用清單
創(chuàng)建向后兼容的UI
?# 優(yōu)化自定義View
創(chuàng)建單元測(cè)試
在UI上顯示Bitmap
建立OpenGL ES的環(huán)境
構(gòu)建表盤(pán)服務(wù)
JNI Tips
建立搜索界面
實(shí)現(xiàn)自定義View的繪制
使用HTTPS與SSL
按需操控BroadcastReceiver
分享簡(jiǎn)單的數(shù)據(jù)
繪制形狀
Android位置信息
創(chuàng)建并運(yùn)行可穿戴應(yīng)用
執(zhí)行 Sync Adpater
獲取最后可知位置
創(chuàng)建 Android 項(xiàng)目
實(shí)現(xiàn)高效的導(dǎo)航
退出全屏的Activity
創(chuàng)建Card
兼容音頻輸出設(shè)備
同步數(shù)據(jù)單元
傳輸數(shù)據(jù)時(shí)避免消耗大量電量
保存到文件
緩存Bitmap
提供配置 Activity
調(diào)度重復(fù)的鬧鐘
實(shí)現(xiàn)輔助功能
重復(fù)的下載是冗余的
隱藏狀態(tài)欄
實(shí)現(xiàn)自定義的網(wǎng)絡(luò)請(qǐng)求
規(guī)劃界面和他們之間的關(guān)系
使用Sync Adapter傳輸數(shù)據(jù)
TV應(yīng)用內(nèi)搜索
響應(yīng)觸摸事件
使用Google Cloud Messaging(已廢棄)
控制相機(jī)
Android網(wǎng)絡(luò)連接與云服務(wù)
請(qǐng)求分享一個(gè)文件
處理TV硬件
響應(yīng)UI可見(jiàn)性的變化
使用網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)
指定輸入法類型
優(yōu)化電池壽命
創(chuàng)建TV應(yīng)用
獲取聯(lián)系人列表
拖拽與縮放
啟動(dòng)與停止線程池中的線程
創(chuàng)建 Sync Adpater
使用 WiFi P2P 服務(wù)發(fā)現(xiàn)
開(kāi)始使用Material Design
代理至新的APIs
使用include標(biāo)簽重用layouts
使得View可交互
高效顯示Bitmap
創(chuàng)建企業(yè)級(jí)應(yīng)用
Fragments之間的交互
創(chuàng)建與執(zhí)行測(cè)試用例
綜合:設(shè)計(jì)我們的樣例 App
繪制表盤(pán)
建立簡(jiǎn)單的用戶界面
自定義動(dòng)畫(huà)
開(kāi)發(fā)輔助服務(wù)
避免出現(xiàn)程序無(wú)響應(yīng)ANR(Keeping Your App Responsive)
使用ViewPager實(shí)現(xiàn)屏幕滑動(dòng)
設(shè)計(jì)高效的導(dǎo)航
Android分享操作(Building Apps with Content Sharing)
提供向后的導(dǎo)航
保持向下兼容
創(chuàng)建TV播放應(yīng)用
縮放View
使用 WiFi 建立 P2P 連接
Android后臺(tái)任務(wù)
連接到網(wǎng)絡(luò)
為 Notification 添加頁(yè)面
使TV應(yīng)用是可被搜索的
添加Action Bar
使用Material的主題
啟動(dòng)另一個(gè)Activity
顯示正在播放卡片
適配不同的系統(tǒng)版本
輕松錄制視頻
創(chuàng)建可穿戴的應(yīng)用
創(chuàng)建自定義的布局
重新創(chuàng)建Activity
使用CursorLoader執(zhí)行查詢?nèi)蝿?wù)
使用舊的APIs實(shí)現(xiàn)新API的效果
使用備份API
安全要點(diǎn)
Android入門(mén)基礎(chǔ):從這里開(kāi)始
保存并搜索數(shù)據(jù)
根據(jù)網(wǎng)絡(luò)連接類型來(lái)調(diào)整下載模式
使用Tabs創(chuàng)建Swipe視圖
SMP(Symmetric Multi-Processor) Primer for Android
解析 XML 數(shù)據(jù)
使用 Volley 傳輸網(wǎng)絡(luò)數(shù)據(jù)
建立ActionBar
Android交互設(shè)計(jì)
使用Intent修改聯(lián)系人信息
增加搜索功能
輕松拍攝照片
定義形狀
測(cè)試你的Activity
在 Notifcation 中接收語(yǔ)音輸入
與其他應(yīng)用的交互
管理系統(tǒng)UI
追蹤手勢(shì)移動(dòng)
Android界面設(shè)計(jì)
執(zhí)行 Android 程序
顯示確認(rèn)界面
創(chuàng)建Lists與Cards
打印HTML文檔
創(chuàng)建TV應(yīng)用
為多屏幕設(shè)計(jì)
定義Shadows與Clipping視圖
使用Fragment建立動(dòng)態(tài)UI
接收Activity返回的結(jié)果
布局變更動(dòng)畫(huà)
定位常見(jiàn)的問(wèn)題
自定義ActionBar的風(fēng)格
定義Layouts
發(fā)送簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求
啟動(dòng)與銷毀Activity
與UI線程通信
非UI線程處理Bitmap
創(chuàng)建TV布局
提升Layout的性能
報(bào)告任務(wù)執(zhí)行狀態(tài)
判斷并監(jiān)測(cè)網(wǎng)絡(luò)連接狀態(tài)
兼容不同的設(shè)備
處理按鍵動(dòng)作
優(yōu)化性能和電池使用時(shí)間
給其他App發(fā)送簡(jiǎn)單的數(shù)據(jù)
Implementing App Restrictions
向后臺(tái)服務(wù)發(fā)送任務(wù)請(qǐng)求
展示Card翻轉(zhuǎn)動(dòng)畫(huà)
管理ViewGroup中的觸摸事件
兼容不同的屏幕密度
通過(guò)藍(lán)牙進(jìn)行調(diào)試
為可穿戴設(shè)備創(chuàng)建Notification
控制音量與音頻播放
獲取聯(lián)系人詳情
在表盤(pán)上顯示信息
提供向上的導(dǎo)航
滾動(dòng)手勢(shì)動(dòng)畫(huà)
幫助用戶在TV上找到內(nèi)容
創(chuàng)建TV導(dǎo)航
為索引指定App內(nèi)容
ActionBar的覆蓋疊加
Android Wear 上的位置檢測(cè)
保護(hù)安全與隱私的最佳策略
Ensuring Compatibility with Managed Profiles
解決云同步的保存沖突
獲取位置更新
創(chuàng)建List
測(cè)試程序
管理網(wǎng)絡(luò)的使用情況
為App內(nèi)容開(kāi)啟深度鏈接
推薦TV內(nèi)容
建立一個(gè)Notification
管理音頻播放
設(shè)計(jì)表盤(pán)
拍照
處理控制器輸入動(dòng)作
判斷并監(jiān)測(cè)設(shè)備的底座狀態(tài)與類型
處理查詢的結(jié)果
保存到數(shù)據(jù)庫(kù)
支持多個(gè)游戲控制器
創(chuàng)建 Stub Content Provider
使得ListView滑動(dòng)順暢
處理數(shù)據(jù)層的事件
創(chuàng)建TV應(yīng)用的第一步
使得你的App內(nèi)容可被Google搜索
將 Notification 放成一疊
創(chuàng)建 Stub 授權(quán)器
暫停與恢復(fù)Activity
管理設(shè)備的喚醒狀態(tài)
Android圖像與動(dòng)畫(huà)
打印照片
云同步
創(chuàng)建TV直播應(yīng)用
為Notification賦加可穿戴特性
提供一個(gè)Card視圖
建立請(qǐng)求隊(duì)列(RequestQueue)
適配不同的語(yǔ)言
創(chuàng)建詳情頁(yè)
測(cè)試UI組件
接收其他設(shè)備的文件
創(chuàng)建自定義View
建立第一個(gè)App
創(chuàng)建2D Picker
監(jiān)測(cè)電池的電量與充電狀態(tài)
打印自定義文檔
抽象出新的APIs
通知提示用戶
獲取文件信息
運(yùn)用投影與相機(jī)視角
在IntentService中執(zhí)行后臺(tái)任務(wù)
多線程操作
創(chuàng)建一個(gè)Fragment
添加Action按鈕
在不同的 Android 系統(tǒng)版本支持控制器
維護(hù)兼容性
發(fā)送文件給其他設(shè)備
創(chuàng)建TV游戲應(yīng)用
創(chuàng)建自定義的View類
代碼性能優(yōu)化建議
Intent過(guò)濾
適配不同的屏幕

JNI Tips

編寫(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é)很有用處。

JavaVM 及 JNIEnv

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)中去)。

jclass, jmethodID, jfieldID

如果你想在本地代碼中訪問(wèn)一個(gè)對(duì)象的字段(field),你可以像下面這樣做:

  • 對(duì)于類,使用FindClass獲得類對(duì)象的引用
  • 對(duì)于字段,使用GetFieldId獲得字段ID
  • 使用對(duì)應(yīng)的方法(例如GetIntField)獲取字段下面的值

類似地,要調(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)刪除。

UTF-8、UTF-16 字符串

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)。

原生類型數(shù)組

JNI提供了一系列函數(shù)來(lái)訪問(wèn)數(shù)組對(duì)象中的內(nèi)容。對(duì)象數(shù)組的訪問(wèn)只能一次一條,但如果原生類型數(shù)組以C方式聲明,則能夠直接進(jìn)行讀寫(xiě)。

為了讓接口更有效率而不受VM實(shí)現(xiàn)的制約,GetArrayElements系列調(diào)用允許運(yùn)行時(shí)返回一個(gè)指向?qū)嶋H元素的指針,或者是分配些內(nèi)存然后拷貝一份。不論哪種方式,返回的原始指針在相應(yīng)的Release調(diào)用之前都保證有效(這意味著,如果數(shù)據(jù)沒(méi)被拷貝,實(shí)際的數(shù)組對(duì)象將會(huì)受到牽制,不能重新成為整理堆空間的一部分)。你必須釋放(Release)每個(gè)你通過(guò)Get得到的數(shù)組。同時(shí),如果Get調(diào)用失敗,你必須確保你的代碼在之后不會(huì)去嘗試調(diào)用Release來(lái)釋放一個(gè)空指針(NULL pointer)。

你可以用一個(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)的那份。

  • 0
    • 真實(shí)的:實(shí)際數(shù)組對(duì)象不受到牽制
    • 拷貝的:數(shù)據(jù)將會(huì)復(fù)制回去,備份空間將會(huì)被釋放。
  • JNI_COMMIT
    • 真實(shí)的:不做任何操作
    • 拷貝的:數(shù)據(jù)將會(huì)復(fù)制回去,備份空間將不會(huì)被釋放。
  • JNI_ABORT
    • 真實(shí)的:實(shí)際數(shù)組對(duì)象不受到牽制.之前的寫(xiě)入不會(huì)被取消。
    • 拷貝的:備份空間將會(huì)被釋放;里面所有的變更都會(huì)丟失。

檢查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。

區(qū)間數(shù)組

當(dāng)你想做的只是拷出或者拷進(jìn)數(shù)據(jù)時(shí),可以選擇調(diào)用像GetArrayElements和GetStringChars這類非常有用的函數(shù)。想想下面:


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):

  • 只需要調(diào)用一個(gè)JNI函數(shù)而是不是兩個(gè),減少了開(kāi)銷。
  • 不需要指針或者額外的拷貝數(shù)據(jù)。
  • 減少了開(kāi)發(fā)人員犯錯(cuò)的風(fēng)險(xiǎn)-在某些失敗之后忘記調(diào)用Release不存在風(fēng)險(xiǎn)。

類似地,你能使用SetArrayRegion函數(shù)拷貝數(shù)據(jù)到數(shù)組,使用GetStringRegion或者GetStringUTFRegion從String中拷貝字符。

異常

當(dāng)異常發(fā)生時(shí)你一定不能調(diào)用大部分的JNI函數(shù)。你的代碼收到異常(通過(guò)函數(shù)的返回值,ExceptionCheck,或者ExceptionOccurred),然后返回,或者清除異常,處理掉。

當(dāng)異常發(fā)生時(shí)你被允許調(diào)用的JNI函數(shù)有:

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

許多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ù)輸出。

擴(kuò)展檢查

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ò)展檢查的。

額外的檢查包括:

  • 數(shù)組:試圖分配一個(gè)長(zhǎng)度為負(fù)的數(shù)組。
  • 壞指針:傳入一個(gè)不完整jarray/jclass/jobject/jstring對(duì)象到JNI函數(shù),或者調(diào)用JNI函數(shù)時(shí)使用空指針傳入到一個(gè)不能為空的參數(shù)中去。
  • 類名:傳入了除“java/lang/String”之外的類名到JNI函數(shù)。
  • 關(guān)鍵調(diào)用:在一個(gè)“關(guān)鍵的(critical)”get和它對(duì)應(yīng)的release之間做出JNI調(diào)用。
  • 直接的ByteBuffers:傳入不正確的參數(shù)到NewDirectByteBuffer。
  • 異常:當(dāng)一個(gè)異常發(fā)生時(shí)調(diào)用了JNI函數(shù)。
  • JNIEnvs:在錯(cuò)誤的線程中使用一個(gè)JNIEnv。
  • jfieldIDs:使用一個(gè)空jfieldID,或者使用jfieldID設(shè)置了一個(gè)錯(cuò)誤類型的值到字段(比如說(shuō),試圖將一個(gè)StringBuilder賦給String類型的域),或者使用一個(gè)靜態(tài)字段下的jfieldID設(shè)置到一個(gè)實(shí)例的字段(instance field)反之亦然,或者使用的一個(gè)類的jfieldID卻來(lái)自另一個(gè)類的實(shí)例。
  • jmethodIDs:當(dāng)調(diào)用Call*Method函數(shù)時(shí)時(shí)使用了類型錯(cuò)誤的jmethodID:不正確的返回值,靜態(tài)/非靜態(tài)的不匹配,this的類型錯(cuò)誤(對(duì)于非靜態(tài)調(diào)用)或者錯(cuò)誤的類(對(duì)于靜態(tài)類調(diào)用)。
  • 引用:在類型錯(cuò)誤的引用上使用了DeleteGlobalRef/DeleteLocalRef。
  • 釋放模式:調(diào)用release使用一個(gè)不正確的釋放模式(其它非 0,JNI_ABORT,JNI_COMMIT的值)。
  • 類型安全:從你的本地代碼中返回了一個(gè)不兼容的類型(比如說(shuō),從一個(gè)聲明返回String的方法卻返回了StringBuilder)。
  • UTF-8:傳入一個(gè)無(wú)效的變形UTF-8字節(jié)序列到JNI調(diào)用。

(方法和域的可訪問(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

本地庫(kù)

你可以使用標(biāo)準(zhǔn)的System.loadLibrary方法來(lái)從共享庫(kù)中加載本地代碼。在你的本地代碼中較好的做法是:

  • 在一個(gè)靜態(tài)類初始化時(shí)調(diào)用System.loadLibrary(見(jiàn)之前的一個(gè)例子中,當(dāng)中就使用了nativeClassInit)。參數(shù)是“未加修飾(undecorated)”的庫(kù)名稱,因此要加載“l(fā)ibfubar.so”,你需要傳入“fubar”。
  • 提供一個(gè)本地函數(shù):jint JNI_OnLoad(JavaVM vm, void reserved)
  • 在JNI_OnLoad中,注冊(cè)所有你的本地方法。你應(yīng)該聲明方法為“靜態(tài)的(static)”因此名稱不會(huì)占據(jù)設(shè)備上符號(hào)表的空間。

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è)查尋及緩存類引用很便利的地方。

64位機(jī)問(wèn)題

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特性:

  • DefineClass沒(méi)有實(shí)現(xiàn)。Android不使用Java字節(jié)碼或者class文件,因此傳入二進(jìn)制class數(shù)據(jù)將不會(huì)有效。

對(duì)Android以前老版本的向后兼容性,你需要注意:

  • 本地函數(shù)的動(dòng)態(tài)查找 在Android 2.0(Eclair)之前,在搜索方法名稱時(shí),字符“$”不會(huì)轉(zhuǎn)換為對(duì)應(yīng)的“_00024”。要使它正常工作需要使用顯式注冊(cè)方式或者將本地方法的聲明移出內(nèi)部類。
  • 分離線程 在Android 2.0(Eclair)之前,使用pthread_key_create析構(gòu)函數(shù)來(lái)避免“退出前線程必須分離”檢查是不可行的(運(yùn)行時(shí)(runtime)也使用了一個(gè)pthread key析構(gòu)函數(shù),因此這是一場(chǎng)看誰(shuí)先被調(diào)用的競(jìng)賽)。
  • 全局弱引用 在Android 2.0(Eclair)之前,全局弱引用沒(méi)有被實(shí)現(xiàn)。如果試圖使用它們,老版本將完全不兼容。你可以使用Android平臺(tái)版本號(hào)常量來(lái)測(cè)試系統(tǒng)的支持性。 在Android 4.0 (Ice Cream Sandwich)之前,全局弱引用只能傳給NewLocalRef, NewGlobalRef, 以及DeleteWeakGlobalRef(強(qiáng)烈建議開(kāi)發(fā)者在使用全局弱引用之前都為它們創(chuàng)建強(qiáng)引用hard reference,所以這不應(yīng)該在所有限制當(dāng)中)。 從Android 4.0 (Ice Cream Sandwich)起,全局弱引用能夠像其它任何JNI引用一樣使用了。
  • 局部引用 在Android 4.0 (Ice Cream Sandwich)之前,局部引用實(shí)際上是直接指針。Ice Cream Sandwich為了更好地支持垃圾回收添加了間接指針,但這并不意味著很多JNI bug在老版本上不存在。更多細(xì)節(jié)見(jiàn)JNI Local Reference Changes in ICS。
  • 使用GetObjectRefType獲得引用類型 在Android 4.0 (Ice Cream Sandwich)之前,使用直接指針(見(jiàn)上面)的后果就是正確地實(shí)現(xiàn)GetObjectRefType是不可能的。我們可以使用依次檢測(cè)全局弱引用表,參數(shù),局部表,全局表的方式來(lái)代替。第一次匹配到你的直接指針時(shí),就表明你的引用類型是當(dāng)前正在檢測(cè)的類型。這意味著,例如,如果你在一個(gè)全局jclass上使用GetObjectRefType,而這個(gè)全局jclass碰巧與作為靜態(tài)本地方法的隱式參數(shù)傳入的jclass一樣的,你得到的結(jié)果是JNILocalRefType而不是JNIGlobalRefType。

FAQ: 為什么出現(xiàn)了UnsatisfiedLinkError?

當(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)原因可能有這些:

  • 庫(kù)文件不存在或者不能被app訪問(wèn)到。使用adb shell ls -l 檢查它的存在性和權(quán)限。
  • 庫(kù)文件不是用NDK構(gòu)建的。這就導(dǎo)致設(shè)備上并不存在它所依賴的函數(shù)或者庫(kù)。

另一種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)的原因有:

  • 庫(kù)文件沒(méi)有得到加載。檢查日志輸出中關(guān)于庫(kù)文件加載的信息。
  • 由于名稱或者簽名錯(cuò)誤,方法不能匹配成功。這通常是由于:
    • 對(duì)于方法的懶查尋,使用 extern "C"和對(duì)應(yīng)的可見(jiàn)性(JNIEXPORT)來(lái)聲明C++函數(shù)沒(méi)有成功。注意Ice Cream Sandwich之前的版本,JNIEXPORT宏是不正確的,因此對(duì)新版本的GCC使用舊的jni.h頭文件將不會(huì)有效。你可以使用arm-eabi-nm查看它們出現(xiàn)在庫(kù)文件里的符號(hào)。如果它們看上去比較凌亂(像_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass這樣而不是Java_Foo_myfunc),或者符號(hào)類型是小寫(xiě)的“t”而不是一個(gè)大寫(xiě)的“T”,這時(shí)你就需要調(diào)整聲明了。
    • 對(duì)于顯式注冊(cè),在進(jìn)行方法簽名時(shí)可能犯了些小錯(cuò)誤。確保你傳入到注冊(cè)函數(shù)的簽名能夠完全匹配上日志文件里提示的。記住“B”是byte,“Z”是boolean。在簽名中類名組件是以“L”開(kāi)頭的,以“;”結(jié)束的,使用“/”來(lái)分隔包名/類名,使用“$”符來(lái)分隔內(nèi)部類名稱(比如說(shuō),Ljava/util/Map$Entry;)。

使用javah來(lái)自動(dòng)生成JNI頭文件也許能幫助你避免這些問(wèn)題。

FAQ: 為什么FindClass不能找到我的類?

確保類名字符串有正確的格式。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)題:

  • 在JNI_OnLoad中使用FindClass查尋一次,然后為后面的使用緩存這些類引用。任何在JNI_OnLoad當(dāng)中執(zhí)行的FindClass調(diào)用都使用與執(zhí)行System.loadLibrary的函數(shù)相關(guān)的類加載器(這個(gè)特例,讓庫(kù)的初始化更加的方便了)。如果你的app代碼正在加載庫(kù)文件,F(xiàn)indClass將會(huì)使用正確的類加載器。
  • 傳入類實(shí)例到一個(gè)需要它的函數(shù),你的本地方法聲明必須帶有一個(gè)Class參數(shù),然后傳入Foo.class。
  • 在合適的地方緩存一個(gè)ClassLoader對(duì)象的引用,然后直接發(fā)起loadClass調(diào)用。這需要額外些工作。

FAQ: 使用本地代碼怎樣共享原始數(shù)據(jù)?

也許你會(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ì)得到提升。