|
導讀
為了在Android OS系統上開發應用程序,Google提供了兩種開發包:SDK和NDK。你可以從Google官方查閱到有許多關於SDK的優秀的書籍、文章作為參考,但Google沒有提供足夠的NDK資料。在現有的書籍中,我認為Cinar O.寫於2012年的”Pro Android C++ with the NDK”值得一讀。
本文旨在幫助那些缺乏Android NDK經驗但又想擴充這方面知識的人們。我所關注的是JNI(本地編程接口,簡稱JNI)。本文分上下兩篇,在上篇中,會從JNI為接口開始講起;下篇會進行回顧,並給出帶兩個文件讀寫功能的實例。
ImportNew注:如果你也對Java技術翻譯分享感興趣,歡迎加入我們的Android開發小組。參與方式請查看小組簡介。
什麼是 Android NDK?
Android NDK(Native Development Kit )是一套工具集合,允許你用像C/C++語言那樣實現應用程序的一部分。
何時使用NDK?
Google僅在極少數情況下建議使用NDK,有如下使用場景:
必須提高性能(例如,對大量數據進行排序)。
使用第三方庫。舉例說明:許多第三方庫由C/C++語言編寫,而Android應用程序需要使用現有的第三方庫,如Ffmpeg、OpenCV這樣的庫。
底層程序設計(例如,應用程序不依賴Dalvik Java虛擬機)。
什麼是JNI?
JNI是一種在Java虛擬機控制下執行代碼的標準機制。代碼被編寫成彙編程序或者C/C++程序,並組裝為動態庫。也就允許了非靜態綁定用法。這提供了一個在Java平台上調用C/C++的一種途徑,反之亦然。
JNI的優勢
與其他類似接口(Netscape Java運行接口、Microsoft的原始本地接口、COM/Java接口)相比,JNI主要的競爭優勢在於:它在設計之初就確保了二進制的兼容性,JNI編寫的應用程序兼容性以及在某些具體平台上的Java虛擬機兼容性(當談及JNI,這裡並不特別針對Dalvik;JNI由Oracle開發,適用於所有Java虛擬機)。這就是為什麼C/C++編譯後的代碼無論在任何平台上都能執行。不過,一些早期版本並不支持二進制兼容。
二進制兼容性是一種程序兼容性類型,允許一個程序在不改變其可執行文件的條件下在不同的編譯環境中工作。
JNI組織結構
圖 — JNI接口指針
這張JNI函數表的組成就像C++的虛函數表。虛擬機可以運行多張函數表,舉例來說,一張調試函數表,另一張是調用函數表。JNI接口指針僅在當前線程中起作用。這意味著指針不能從一個線程進入另一個線程。然而,可以在不同的線程中調用本地方法。
示例代碼:- jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
- {
- const char *str = (*env)->GetStringUTFChars(env, s, 0);
- (*env)->ReleaseStringUTFChars(env, s, str);
- return 10;
- }
複製代碼 *env — 一個接口指針。
obj — 在本地方法中聲明的對象引用。
i和s — 用於傳遞的參數。
原始類型(Primitive Type)在虛擬機和本機代碼進行拷貝,對象之間使用引用進行傳遞。VM(虛擬機)要追踪所有傳遞給本地代碼的對象引用。GC無法釋放所有傳遞給本地代碼的對象引用。與此同時,本機代碼應該通知VM不需要的對象引用。
局部引用和全局引用
JNI定義了三種引用類型:局部引用、全局引用和全局弱引用。局部引用在方法完成之前是有效的。所有通過JNI函數返回的Java對像都是本地引用。程序員希望VM會清空所有的局部引用,然而局部引用僅在其創建的線程裡可用。如果有必要,局部引用可以通過接口中的DeleteLocalRef JNI方法立即釋放:- jclass clazz;
- clazz = (*env)->FindClass(env, "java/lang/String");
- ...
- (*env)->DeleteLocalRef(env, clazz)
複製代碼 全局引用在完全釋放之前都是有效的。要創建一個全局引用,需要調用NewGlobalRef方法。如果全局引用並不是必須的,可以通過DeleteGlobalRef方法刪除:- jclass localClazz;
- jclass globalClazz;
- ...
- localClazz = (*env)->FindClass(env, "java/lang/String");
- globalClazz = (*env)->NewGlobalRef(env, localClazz);
- ...
- (*env)->DeleteLocalRef(env, localClazz);
複製代碼 錯誤
JNI不會檢查NullPointerException、IllegalArgumentException這樣的錯誤,原因是:
導致性能下降。
在絕大多數C的庫函數中,很難避免錯誤發生。
JNI允許用戶使用Java異常處理。大部分JNI方法會返回錯誤代碼但本身並不會報出異常。因此,很有必要在代碼本身進行處理,將異常拋給Java。在JNI內部,首先會檢查調用函數返回的錯誤代碼,之後會調用ExpectOccurred()返回一個錯誤對象。- jthrowable ExceptionOccurred(JNIEnv *env);
複製代碼 例如:一些操作數組的JNI函數不會報錯,因此可以調用ArrayIndexOutofBoundsException或ArrayStoreExpection方法報告異常。
JNI原始類型
JNI有自己的原始數據類型和數據引用類型。
Java類型 | | 描述 | boolean(布爾型) | jboolean | 無符號8個比特 | byte(字節型) | jbyte | 有符號8個比特 | char(字符型) | jchar | 無符號16個比特 | short(短整型) | jshort | 有符號16個比特 | int(整型) | jint | 有符號32個比特 | long(長整型) | jlong | 有符號64個比特 | float(浮點型) | jfloat | 32個比特 | double(雙精度浮點型) | jdouble | 64個比特 | void(空型) | void | N/A
| JNI引用類型
圖 — JNI引用類型
改進的UTF-8編碼
JNI使用改進的UTF-8字符串來表示不同的字符類型。Java使用UTF-16編碼。UTF-8編碼主要使用於C語言,因為它的編碼用\u000表示為0xc0,而不是通常的0×00。非空ASCII字符改進後的字符串編碼中可以用一個字節表示。 |
|