在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

JNI Tips

編寫:pedant - 原文:http://developer.android.com/training/articles/perf-jni.html

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

如果你對JNI還不是太熟悉,可以先通讀Java Native Interface Specification這篇文章來對JNI如何工作以及哪些特性可用有個大致的印象。這種接口的一些方面不能立即一讀就顯而易見,所以你會發(fā)現(xiàn)接下來的幾個章節(jié)很有用處。

JavaVM 及 JNIEnv

JNI定義了兩種關鍵數(shù)據(jù)結構,“JavaVM”和“JNIEnv”。它們本質(zhì)上都是指向函數(shù)表指針的指針(在C++版本中,它們被定義為類,該類包含一個指向函數(shù)表的指針,以及一系列可以通過這個函數(shù)表間接地訪問對應的JNI函數(shù)的成員函數(shù))。JavaVM提供“調(diào)用接口(invocation interface)”函數(shù), 允許你創(chuàng)建和銷毀一個JavaVM。理論上你可以在一個進程中擁有多個JavaVM對象,但安卓只允許一個。

JNIEnv提供了大部分JNI功能。你定義的所有本地函數(shù)都會接收JNIEnv作為第一個參數(shù)。

JNIEnv是用作線程局部存儲。因此,你不能在線程間共享一個JNIEnv變量。如果在一段代碼中沒有其它辦法獲得它的JNIEnv,你可以共享JavaVM對象,使用GetEnv來取得該線程下的JNIEnv(如果該線程有一個JavaVM的話;見下面的AttachCurrentThread)。

JNIEnv和JavaVM的在C聲明是不同于在C++的聲明。頭文件“jni.h”根據(jù)它是以C還是以C++模式包含來提供不同的類型定義(typedefs)。因此,不建議把JNIEnv參數(shù)放到可能被兩種語言引入的頭文件中(換一句話說:如果你的頭文件需要#ifdef __cplusplus,你可能不得不在任何涉及到JNIEnv的內(nèi)容處都要做些額外的工作)。

線程

所有的線程都是Linux線程,由內(nèi)核統(tǒng)一調(diào)度。它們通常從托管代碼中啟動(使用Thread.start),但它們也能夠在其他任何地方創(chuàng)建,然后連接(attach)到JavaVM。例如,一個用pthread_create啟動的線程能夠使用JNI AttachCurrentThread 或 AttachCurrentThreadAsDaemon函數(shù)連接到JavaVM。在一個線程成功連接(attach)之前,它沒有JNIEnv,不能夠調(diào)用JNI函數(shù)。

連接一個本地環(huán)境創(chuàng)建的線程會觸發(fā)構造一個java.lang.Thread對象,然后其被添加到主線程群組(main ThreadGroup),以讓調(diào)試器可以探測到。對一個已經(jīng)連接的線程使用AttachCurrentThread不做任何操作(no-op)。

安卓不能中止正在執(zhí)行本地代碼的線程。如果正在進行垃圾回收,或者調(diào)試器已發(fā)出了中止請求,安卓會在下一次調(diào)用JNI函數(shù)的時候中止線程。

連接過的(attached)線程在它們退出之前必須通過JNI調(diào)用DetachCurrentThread。如果你覺得直接這樣編寫不太優(yōu)雅,在安卓2.0(Eclair)及以上, 你可以使用pthread_key_create來定義一個析構函數(shù),它將會在線程退出時被調(diào)用,你可以在那兒調(diào)用DetachCurrentThread (使用生成的key與pthread_setspecific將JNIEnv存儲到線程局部空間內(nèi);這樣JNIEnv能夠作為參數(shù)傳入到析構函數(shù)當中去)。

jclass, jmethodID, jfieldID

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

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

類似地,要調(diào)用一個方法,你首先得獲得一個類對象的引用,然后是方法ID(method ID)。這些ID通常是指向運行時內(nèi)部數(shù)據(jù)結構。查找到它們需要些字符串比較,但一旦你實際去執(zhí)行它們獲得字段或者做方法調(diào)用是非??斓?。

如果性能是你看重的,那么一旦查找出這些值之后在你的本地代碼中緩存這些結果是非常有用的。因為每個進程當中的JavaVM是存在限制的,存儲這些數(shù)據(jù)到本地靜態(tài)數(shù)據(jù)結構中是非常合理的。

類引用(class reference),字段ID(field ID)以及方法ID(method ID)在類被卸載前都是有效的。如果與一個類加載器(ClassLoader)相關的所有類都能夠被垃圾回收,但是這種情況在安卓上是罕見甚至不可能出現(xiàn),只有這時類才被卸載。注意雖然jclass是一個類引用,但是必須要調(diào)用NewGlobalRef保護起來(見下個章節(jié))。

當一個類被加載時如果你想緩存些ID,而后當這個類被卸載后再次載入時能夠自動地更新這些緩存ID,正確做法是在對應的類中添加一段像下面的代碼來初始化這些ID:


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

static {
    nativeInit();
}

在你的C/C++代碼中創(chuàng)建一個nativeClassInit方法以完成ID查找的工作。當這個類被初始化時這段代碼將會執(zhí)行一次。當這個類被卸載后而后再次載入時,這段代碼將會再次執(zhí)行。

局部和全局引用

每個傳入本地方法的參數(shù),以及大部分JNI函數(shù)返回的每個對象都是“局部引用”。這意味著它只在當前線程的當前方法執(zhí)行期間有效。即使這個對象本身在本地方法返回之后仍然存在,這個引用也是無效的。

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

如果你想持有一個引用更長的時間,你就必須使用一個全局(“global”)引用了。NewGlobalRef函數(shù)以一個局部引用作為參數(shù)并且返回一個全局引用。全局引用能夠保證在你調(diào)用DeleteGlobalRef前都是有效的。

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


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

所有的JNI方法都接收局部引用和全局引用作為參數(shù)。相同對象的引用卻可能具有不同的值。例如,用相同對象連續(xù)地調(diào)用NewGlobalRef得到返回值可能是不同的。為了檢查兩個引用是否指向的是同一個對象,你必須使用IsSameObject函數(shù)。絕不要在本地代碼中用==符號來比較兩個引用。

得出的結論就是你絕不要在本地代碼中假定對象的引用是常量或者是唯一的。代表一個對象的32位值從方法的一次調(diào)用到下一次調(diào)用可能有不同的值。在連續(xù)的調(diào)用過程中兩個不同的對象卻可能擁有相同的32位值。不要使用jobject的值作為key.

開發(fā)者需要“不過度分配”局部引用。在實際操作中這意味著如果你正在創(chuàng)建大量的局部引用,或許是通過對象數(shù)組,你應該使用DeleteLocalRef手動地釋放它們,而不是寄希望JNI來為你做這些。實現(xiàn)上只預留了16個局部引用的空間,所以如果你需要更多,要么你刪掉以前的,要么使用EnsureLocalCapacity/PushLocalFrame來預留更多。

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

還有一種不常見的情況值得一提,如果你使用AttachCurrentThread連接(attach)了本地進程,正在運行的代碼在線程分離(detach)之前決不會自動釋放局部引用。你創(chuàng)建的任何局部引用必須手動刪除。通常,任何在循環(huán)中創(chuàng)建局部引用的本地代碼可能都需要做一些手動刪除。

UTF-8、UTF-16 字符串

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

如果可能的話,直接操作UTF-16字符串通常更快些。安卓當前在調(diào)用GetStringChars時不需要拷貝,而GetStringUTFChars需要一次分配并且轉換為UTF-8格式。注意UTF-16字符串不是以零終止字符串,\u0000是被允許的,所以你需要像對jchar指針一樣地處理字符串的長度。

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

傳入NewStringUTF函數(shù)的數(shù)據(jù)必須是變形UTF-8格式。一種常見的錯誤情況是,從文件或者網(wǎng)絡流中讀取出的字符數(shù)據(jù),沒有過濾直接使用NewStringUTF處理。除非你確定數(shù)據(jù)是7位的ASCII格式,否則你需要剔除超出7位ASCII編碼范圍(high-ASCII)的字符或者將它們轉換為對應的變形UTF-8格式。如果你沒那樣做,UTF-16的轉換結果可能不會是你想要的結果。JNI擴展檢查將會掃描字符串,然后警告你那些無效的數(shù)據(jù),但是它們將不會發(fā)現(xiàn)所有潛在的風險。

原生類型數(shù)組

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

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

你可以用一個非空指針作為isCopy參數(shù)的值來決定數(shù)據(jù)是否會被拷貝。這相當有用。

Release類的函數(shù)接收一個mode參數(shù),這個參數(shù)的值可選的有下面三種。而運行時具體執(zhí)行的操作取決于它返回的指針是指向真實數(shù)據(jù)還是拷貝出來的那份。

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

檢查isCopy標識的一個原因是對一個數(shù)組做出變更后確認你是否需要傳入JNI_COMMIT來調(diào)用Release函數(shù)。如果你交替地執(zhí)行變更和讀取數(shù)組內(nèi)容的代碼,你也許可以跳過無操作(no-op)的JNI_COMMIT。檢查這個標識的另一個可能的原因是使用JNI_ABORT可以更高效。例如,你也許想得到一個數(shù)組,適當?shù)匦薷乃?,傳入部分到其他函?shù)中,然后丟掉這些修改。如果你知道JNI是為你做了一份新的拷貝,就沒有必要再創(chuàng)建另一份“可編輯的(editable)”的拷貝了。如果JNI傳給你的是原始數(shù)組,這時你就需要創(chuàng)建一份你自己的拷貝了。

另一個常見的錯誤(在示例代碼中出現(xiàn)過)是認為當isCopy是false時你就可以不調(diào)用Release。實際上是沒有這種情況的。如果沒有分配備份空間,那么初始的內(nèi)存空間會受到牽制,位置不能被垃圾回收器移動。

另外注意JNI_COMMIT標識沒有釋放數(shù)組,你最終需要使用一個不同的標識再次調(diào)用Release。

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

當你想做的只是拷出或者拷進數(shù)據(jù)時,可以選擇調(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ù)組,從當中拷貝出開頭的len個字節(jié)元素,然后釋放這個數(shù)組。根據(jù)代碼的實現(xiàn),Get函數(shù)將會牽制或者拷貝數(shù)組的內(nèi)容。上面的代碼拷貝了數(shù)據(jù)(為了可能的第二次),然后調(diào)用Release;這當中JNI_ABORT確保不存在第三份拷貝了。

另一種更簡單的實現(xiàn)方式:


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

這種方式有幾個優(yōu)點:

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

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

異常

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

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

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

許多JNI調(diào)用能夠拋出異常,但通常提供一種簡單的方式來檢查失敗。例如,如果NewString返回一個非空值,你不需要檢查異常。然而,如果你調(diào)用一個方法(使用一個像CalllObjectMethod的函數(shù)),你必須一直檢查異常,因為當一個異常拋出時它的返回值將不會是有效的。

注意中斷代碼拋出的異常不會展開本地調(diào)用堆棧信息,Android也還不支持C++異常。JNI Throw和ThrowNew指令僅僅是在當前線程中放入一個異常指針。從本地代碼返回到托管代碼時,異常將會被注意到,得到適當?shù)奶幚怼?/p>

本地代碼能夠通過調(diào)用ExceptionCheck或者ExceptionOccurred捕獲到異常,然后使用ExceptionClear清除掉。通常,拋棄異常而不處理會導致些問題。

沒有內(nèi)建的函數(shù)來處理Throwable對象自身,因此如果你想得到異常字符串,你需要找出Throwable Class,然后查找到getMessage "()Ljava/lang/String;"的方法ID,調(diào)用它,如果結果非空,使用GetStringUTFChars,得到的結果你可以傳到printf(3) 或者其它相同功能的函數(shù)輸出。

擴展檢查

JNI的錯誤檢查很少。錯誤發(fā)生時通常會導致崩潰。Android也提供了一種模式,叫做CheckJNI,這當中JavaVM和JNIEnv函數(shù)表指針被換成了函數(shù)表,它在調(diào)用標準實現(xiàn)之前執(zhí)行了一系列擴展檢查的。

額外的檢查包括:

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

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

有幾種方法去啟用CheckJNI。

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

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


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

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


D AndroidRuntime: CheckJNI is ON

如果你有一臺常規(guī)的設備,你可以使用下面的命令:


adb shell setprop debug.checkjni 1

這將不會影響已經(jīng)在運行的app,但是從那以后啟動的任何app都將打開CheckJNI(改變屬性為其它值或者只是重啟都將會再次關閉CheckJNI)。這種情況下,你將會在下一次app啟動時,在日志輸出中看到如下字符:


D Late-enabling CheckJNI

本地庫

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

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

JNI_OnLoad函數(shù)在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;
}

你也可以使用共享庫的全路徑來調(diào)用System.load。對于Android app,你也許會發(fā)現(xiàn)從context對象中得到應用私有數(shù)據(jù)存儲的全路徑是非常有用的。

上面是推薦的方式,但不是僅有的實現(xiàn)方式。顯式注冊不是必須的,提供一個JNI_OnLoad函數(shù)也不是必須的。你可以使用基于特殊命名的“發(fā)現(xiàn)(discovery)”模式來注冊本地方法(更多細節(jié)見:JNI spec),雖然這并不可取。因為如果一個方法的簽名錯誤,在這個方法實際第一次被調(diào)用之前你是不會知道的。

關于JNI_OnLoad另一點注意的是:任何你在JNI_OnLoad中對FindClass的調(diào)用都發(fā)生在用作加載共享庫的類加載器的上下文(context)中。一般FindClass使用與“調(diào)用?!表敳糠椒ㄏ嚓P的加載器,如果當中沒有加載器(因為線程剛剛連接)則使用“系統(tǒng)(system)”類加載器。這就使得JNI_OnLoad成為一個查尋及緩存類引用很便利的地方。

64位機問題

Android當前設計為運行在32位的平臺上。理論上它也能夠構建為64位的系統(tǒng),但那不是現(xiàn)在的目標。當與本地代碼交互時,在大多數(shù)情況下這不是你需要擔心的,但是如果你打算存儲指針變量到對象的整型字段(integer field)這樣的本地結構中,這就變得非常重要了。為了支持使用64位指針的架構,你需要使用long類型而不是int類型的字段來存儲你的本地指針。

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

除了下面的例外,支持所有的JNI 1.6特性:

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

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

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

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

當使用本地代碼開發(fā)時經(jīng)常會見到像下面的錯誤:


java.lang.UnsatisfiedLinkError: Library foo not found

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

你遇到“l(fā)ibrary not found”異常的常見原因可能有這些:

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

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


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

在日志中,你會發(fā)現(xiàn):


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

這意味著運行時嘗試匹配一個方法但是沒有成功,這種情況常見的原因有:

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

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

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

確保類名字符串有正確的格式。JNI類名稱以包名開始,然后使用左斜杠來分隔,比如java/lang/String。如果你正在查找一個數(shù)組類,你需要以對應數(shù)目的綜括號開頭,使用“L”和“;”將類名兩頭包起來,所以一個一維字符串數(shù)組應該寫成[Ljava/lang/String;。

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


Foo.myfunc(Native Method)
Foo.main(Foo.java:10)
dalvik.system.NativeStart.main(Native Method)

最頂層的方法是Foo.myfunc。FindClass找到與類Foo相關的ClassLoader對象然后使用它。

這通常正是你所想的。如果你創(chuàng)建了自己的線程那么就會遇到麻煩(也許是調(diào)用了pthread_create然后使用AttachCurrentThread進行了連接)?,F(xiàn)在跟蹤堆??赡芟裣旅孢@樣:


dalvik.system.NativeStart.run(Native Method)

最頂層的方法是NativeStart.run,它不是你應用內(nèi)的方法。如果你從這個線程中調(diào)用FindClass,JavaVM將會啟動“系統(tǒng)(system)”的而不是與你應用相關的加載器,因此試圖查找應用內(nèi)定義的類都將會失敗。

下面有幾種方法可以解決這個問題:

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

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

也許你會遇到這樣一種情況,想從你的托管代碼或者本地代碼訪問一大塊原始數(shù)據(jù)的緩沖區(qū)。常見例子包括對bitmap或者聲音文件的處理。這里有兩種基本實現(xiàn)方式。

你可以將數(shù)據(jù)存儲到byte[]。這允許你從托管代碼中快速地訪問。然而,在本地代碼端不能保證你不去拷貝一份就直接能夠訪問數(shù)據(jù)。在某些實現(xiàn)中,GetByteArrayElements和GetPrimitiveArrayCritical將會返回指向在維護堆中的原始數(shù)據(jù)的真實指針,但是在另外一些實現(xiàn)中將在本地堆空間分配一塊緩沖區(qū)然后拷貝數(shù)據(jù)過去。

還有一種選擇是將數(shù)據(jù)存儲在一塊直接字節(jié)緩沖區(qū)(direct byte buffer),可以使用java.nio.ByteBuffer.allocateDirect或者NewDirectByteBuffer JNI函數(shù)創(chuàng)建buffer。不像常規(guī)的byte緩沖區(qū),它的存儲空間將不會分配在程序維護的堆空間上,總是可以從本地代碼直接訪問(使用GetDirectBufferAddress得到地址)。依賴于直接字節(jié)緩沖區(qū)訪問的實現(xiàn)方式,從托管代碼訪問原始數(shù)據(jù)將會非常慢。

選擇使用哪種方式取決于兩個方面:

1.大部分的數(shù)據(jù)訪問是在Java代碼還是C/C++代碼中發(fā)生?

2.如果數(shù)據(jù)最終被傳到系統(tǒng)API,那它必須是怎樣的形式(例如,如果數(shù)據(jù)最終被傳到一個使用byte[]作為參數(shù)的函數(shù),在直接的ByteBuffer中處理或許是不明智的)?

如果通過上面兩種情況仍然不能明確區(qū)分的,就使用直接字節(jié)緩沖區(qū)(direct byte buffer)形式。它們的支持是直接構建到JNI中的,在未來的版本中性能可能會得到提升。