新聞中心
導(dǎo)讀

創(chuàng)新互聯(lián)公司2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站建設(shè)、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元祁連做網(wǎng)站,已為上家服務(wù),為祁連各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575
為了在Android OS系統(tǒng)上開(kāi)發(fā)應(yīng)用程序,Google提供了兩種開(kāi)發(fā)包:SDK和NDK。你可以從Google官方查閱到有許多關(guān)于SDK 的優(yōu)秀的書(shū)籍、文章作為參考,但Google沒(méi)有提供足夠的NDK資料。在現(xiàn)有的書(shū)籍中,我認(rèn)為Cinar O.寫(xiě)于2012年 的”P(pán)ro Android C++ with the NDK”值得一讀。
本文旨在幫助那些缺乏Android NDK經(jīng)驗(yàn)但又想擴(kuò)充這方面知識(shí)的人們。我所關(guān)注的是JNI(本地編程接口,簡(jiǎn)稱JNI)。本文分上下兩篇,在上篇中,會(huì)從JNI為接口開(kāi)始講起;下篇會(huì)進(jìn)行回顧,并給出帶兩個(gè)文件讀寫(xiě)功能的實(shí)例。
什么是 Android NDK?
Android NDK(Native Development Kit )是一套工具集合,允許你用像C/C++語(yǔ)言那樣實(shí)現(xiàn)應(yīng)用程序的一部分。
何時(shí)使用NDK?
Google僅在極少數(shù)情況下建議使用NDK,有如下使用場(chǎng)景:
- 必須提高性能(例如,對(duì)大量數(shù)據(jù)進(jìn)行排序)。
- 使用第三方庫(kù)。舉例說(shuō)明:許多第三方庫(kù)由C/C++語(yǔ)言編寫(xiě),而Android應(yīng)用程序需要使用現(xiàn)有的第三方庫(kù),如Ffmpeg、OpenCV這樣的庫(kù)。
- 底層程序設(shè)計(jì)(例如,應(yīng)用程序不依賴Dalvik Java虛擬機(jī))。
什么是JNI?
JNI是一種在Java虛擬機(jī)控制下執(zhí)行代碼的標(biāo)準(zhǔn)機(jī)制。代碼被編寫(xiě)成匯編程序或者C/C++程序,并組裝為動(dòng)態(tài)庫(kù)。也就允許了非靜態(tài)綁定用法。這提供了一個(gè)在Java平臺(tái)上調(diào)用C/C++的一種途徑,反之亦然。
JNI的優(yōu)勢(shì)
與其他類似接口(Netscape Java運(yùn)行接口、Microsoft的原始本地接口、COM/Java接口)相比,JNI主要的競(jìng)爭(zhēng)優(yōu)勢(shì)在于:它在設(shè)計(jì)之初就確保了二進(jìn)制的兼容 性,JNI編寫(xiě)的應(yīng)用程序兼容性以及在某些具體平臺(tái)上的Java虛擬機(jī)兼容性(當(dāng)談及JNI,這里并不特別針對(duì)Dalvik;JNI由Oracle開(kāi)發(fā), 適用于所有Java虛擬機(jī))。這就是為什么C/C++編譯后的代碼無(wú)論在任何平臺(tái)上都能執(zhí)行。不過(guò),一些早期版本并不支持二進(jìn)制兼容。
二進(jìn)制兼容性是一種程序兼容性類型,允許一個(gè)程序在不改變其可執(zhí)行文件的條件下在不同的編譯環(huán)境中工作。
JNI組織結(jié)構(gòu)
圖1 — JNI接口指針
這張JNI函數(shù)表的組成就像C++的虛函數(shù)表。虛擬機(jī)可以運(yùn)行多張函數(shù)表,舉例來(lái)說(shuō),一張調(diào)試函數(shù)表,另一張是調(diào)用函數(shù)表。JNI接口指針僅在當(dāng)前線程中起作用。這意味著指針不能從一個(gè)線程進(jìn)入另一個(gè)線程。然而,可以在不同的線程中調(diào)用本地方法。
示例代碼:
- 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 — 一個(gè)接口指針。
- obj — 在本地方法中聲明的對(duì)象引用。
- i和s — 用于傳遞的參數(shù)。
原始類型(Primitive Type)在虛擬機(jī)和本機(jī)代碼進(jìn)行拷貝,對(duì)象之間使用引用進(jìn)行傳遞。VM(虛擬機(jī))要追蹤所有傳遞給本地代碼的對(duì)象引用。GC無(wú)法釋放所有傳遞給本地代碼的對(duì)象引用。與此同時(shí),本機(jī)代碼應(yīng)該通知VM不需要的對(duì)象引用。
局部引用和全局引用
JNI定義了三種引用類型:局部引用、全局引用和全局弱引用。局部引用在方法完成之前是有效的。所有通過(guò)JNI函數(shù)返回的Java對(duì)象都是本地引 用。程序員希望VM會(huì)清空所有的局部引用,然而局部引用僅在其創(chuàng)建的線程里可用。如果有必要,局部引用可以通過(guò)接口中的DeleteLocalRef JNI方法立即釋放:
- jclass clazz;
- clazz = (*env)->FindClass(env, "java/lang/String");
- ...
- (*env)->DeleteLocalRef(env, clazz)
全局引用在完全釋放之前都是有效的。要?jiǎng)?chuàng)建一個(gè)全局引用,需要調(diào)用NewGlobalRef方法。如果全局引用并不是必須的,可以通過(guò)DeleteGlobalRef方法刪除:
- jclass localClazz;
- jclass globalClazz;
- ...
- localClazz = (*env)->FindClass(env, "java/lang/String");
- globalClazz = (*env)->NewGlobalRef(env, localClazz);
- ...
- (*env)->DeleteLocalRef(env, localClazz);
錯(cuò)誤
JNI不會(huì)檢查NullPointerException、IllegalArgumentException這樣的錯(cuò)誤,原因是:
- 導(dǎo)致性能下降。
- 在絕大多數(shù)C的庫(kù)函數(shù)中,很難避免錯(cuò)誤發(fā)生。
JNI允許用戶使用Java異常處理。大部分JNI方法會(huì)返回錯(cuò)誤代碼但本身并不會(huì)報(bào)出異常。因此,很有必要在代碼本身進(jìn)行處理,將異常拋給Java。在JNI內(nèi)部,首先會(huì)檢查調(diào)用函數(shù)返回的錯(cuò)誤代碼,之后會(huì)調(diào)用ExpectOccurred()返回一個(gè)錯(cuò)誤對(duì)象。
- jthrowable ExceptionOccurred(JNIEnv *env);
例如:一些操作數(shù)組的JNI函數(shù)不會(huì)報(bào)錯(cuò),因此可以調(diào)用ArrayIndexOutofBoundsException或ArrayStoreExpection方法報(bào)告異常。
JNI原始類型
JNI有自己的原始數(shù)據(jù)類型和數(shù)據(jù)引用類型。
|
Java類型 |
本地類型(JNI) |
描述 |
| boolean(布爾型) | jboolean | 無(wú)符號(hào)8個(gè)比特 |
| byte(字節(jié)型) | jbyte | 有符號(hào)8個(gè)比特 |
| char(字符型) | jchar | 無(wú)符號(hào)16個(gè)比特 |
| short(短整型) | jshort | 有符號(hào)16個(gè)比特 |
| int(整型) | jint | 有符號(hào)32個(gè)比特 |
| long(長(zhǎng)整型) | jlong | 有符號(hào)64個(gè)比特 |
| float(浮點(diǎn)型) | jfloat | 32個(gè)比特 |
| double(雙精度浮點(diǎn)型) | jdouble | 64個(gè)比特 |
| void(空型) | void | N/A |
JNI引用類型
圖2 — JNI引用類型
改進(jìn)的UTF-8編碼
JNI使用改進(jìn)的UTF-8字符串來(lái)表示不同的字符類型。Java使用UTF-16編碼。UTF-8編碼主要使用于C語(yǔ)言,因?yàn)樗木幋a用\u000表示為0xc0,而不是通常的0×00。非空ASCII字符改進(jìn)后的字符串編碼中可以用一個(gè)字節(jié)表示。
#p#
JNI函數(shù):
JNI接口不僅有自己的數(shù)據(jù)集(dataset)也有自己的函數(shù)?;仡欉@些數(shù)據(jù)集和函數(shù)需要花費(fèi)我們很多時(shí)間??梢詮墓俜轿臋n中找到更多信息:
http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html
JNI函數(shù)使用示例
下面會(huì)通過(guò)一個(gè)簡(jiǎn)短的示例確保你對(duì)這些資料所講的內(nèi)容有了正確的理解:
- #include
- ...
- JavaVM *jvm;
- JNIEnv *env;
- JavaVMInitArgs vm_args;
- JavaVMOption* options = new JavaVMOption[1];
- options[0].optionString = "-Djava.class.path=/usr/lib/java";
- vm_args.version = JNI_VERSION_1_6;
- vm_args.nOptions = 1;
- vm_args.options = options;
- vm_args.ignoreUnrecognized = false;
- JNI_CreateJavaVM(&jvm, &env, &vm_args);
- delete options;
- jclass cls = env->FindClass("Main");
- jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
- env->CallStaticVoidMethod(cls, mid, 100);
- jvm->DestroyJavaVM();
讓我們來(lái)逐個(gè)分析字符串:
- JavaVM — 提供了一個(gè)接口,可以調(diào)用函數(shù)創(chuàng)建、刪除Java虛擬機(jī)。
- JNIEnv — 確保了大多數(shù)的JNI函數(shù)。
- JavaVMlnitArgs — Java虛擬機(jī)參數(shù)。
- JavaVMOption — Java虛擬機(jī)選項(xiàng)。
JNI的_CreateJavaVM()方法初始化Java虛擬機(jī)并向JNI接口返回一個(gè)指針。
JNI_DestroyJavaVM()方法可以載入創(chuàng)建好的Java虛擬機(jī)。
線程
內(nèi)核負(fù)責(zé)管理所有在Linux上運(yùn)行的線程;線程通過(guò)AttachCurrentThread和AttachCurrentThreadAsDaemon函數(shù)附加到Java虛擬機(jī)。如果線程沒(méi)有被添加成功,則不能訪問(wèn)JNIEnv。 Android系統(tǒng)不能停止JNI創(chuàng)建的線程,即使GC(Garbage Collection)在運(yùn)行釋放內(nèi)存時(shí)也不行。直到調(diào)用DetachCurrentThread方法,該線程才會(huì)從Java虛擬機(jī)脫離。
***步
你的項(xiàng)目結(jié)構(gòu)應(yīng)該如圖3所示:
圖3—工程結(jié)構(gòu)
在圖3中,所有本地代碼都存儲(chǔ)到一個(gè)jni的文件夾。在新建一個(gè)工程后,Libs文件夾會(huì)被分為四個(gè)子文件夾。這意味著一個(gè)子目錄對(duì)應(yīng)一種處理器架構(gòu),庫(kù)的數(shù)量取決于處理器架構(gòu)的數(shù)量。
要?jiǎng)?chuàng)建一個(gè)本地項(xiàng)目和一個(gè)Android項(xiàng)目可以參照以下面的步驟:
- 創(chuàng)建一個(gè)jni文件夾 — 包含本地代碼的項(xiàng)目源代碼根目錄。
- 創(chuàng)建一個(gè)Android.mk文件用來(lái)構(gòu)建項(xiàng)目。
- 創(chuàng)建一個(gè)Application.mk文件用來(lái)存儲(chǔ)編譯參數(shù)。雖然這不是必須的配置,但是推薦你這么做。這樣會(huì)使得編譯設(shè)置更加靈活。
- 創(chuàng)建一個(gè)ndk-build文件以此來(lái)顯示編譯過(guò)程(同樣這一步也不是必須的)。
Android.mk
就像前面提到的,Android.mk是編譯本地項(xiàng)目的makefile。Android.mk把代碼按照模塊進(jìn)行了劃分,把靜態(tài)庫(kù)(static library)拷貝到項(xiàng)目的libs文件夾,生成共享庫(kù)(shared library)和獨(dú)立的可執(zhí)行文件。
最精簡(jiǎn)的配置示例:
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := NDKBegining
- LOCAL_SRC_FILES := ndkBegining.c
- include $(BUILD_SHARED_LIBRARY)
讓我們來(lái)仔細(xì)看看:
- LOCAL_PATH:-$(call my-dir) — 調(diào)用函數(shù)宏my-dir返回當(dāng)前文件所在路徑。
- include $(CLEAR_VARS) — 清除所有LOCAL_PATH以外的變量。這是必須的步驟,考慮到所有編譯控制文件都位于同一個(gè)GNU MAKE執(zhí)行環(huán)境中,所有變量都是全局的。
- LOCAL_MODULE — 輸出模塊名稱。在上述例子中,輸出模塊叫做NDKBegining。但是在生成以后,會(huì)在libs文件夾中創(chuàng)建libNDKbegining庫(kù)。同 時(shí),Android系統(tǒng)會(huì)為其添加一個(gè)前綴名lib,例如一個(gè)被命名為”foo”的共享庫(kù)模塊,將會(huì)生成”libfoo.so”文件。 但是在Java代 碼中使用庫(kù)時(shí)應(yīng)該忽略前綴名(也就是說(shuō),名稱應(yīng)該和makefile一樣)。
- LOCAL_SRC_FILE — 列出編譯所需要的源文件。
- include $(BUILD_SHARED_LIBARY) — 輸出模塊的類型。
你可以在Android.mk文件中設(shè)置自定義變量;但是必須遵守語(yǔ)法命名規(guī)則:LOCAL_、PRIVATE_、NDK_、APP_、my-dir。Google建議自定義示例前綴使用MY_,例如:
MY_SOURCE := NDKBegining.c
這樣就調(diào)用了一個(gè)變量$(MY_SOURCE)。變量同樣也可以被連接起來(lái),例如:
LOCAL_SRC_FILES += $(MY_SOURCE)
Application.mk
這個(gè)makefile中定義了好幾種變量讓編譯更加靈活:
- APP_OPTM — 這個(gè)變量是可選的,用于指定程序是“release”還是“debug”。在構(gòu)建應(yīng)用程序模塊時(shí),該變量用來(lái)優(yōu)化構(gòu)建過(guò)程。你可以在調(diào)試中指定“release”,不過(guò)“debug”支持的配置選項(xiàng)更多。
- APP_BUILD_SCRI為Android.mk定義了另一條路徑。
- APP_ABI — 最重要的變量之一。它指定了編譯模塊時(shí)使用的目標(biāo)處理器架構(gòu)。默認(rèn)情況下,APP_ABI會(huì)設(shè)置為“armeabi”,對(duì)應(yīng)于ARMv5TE架構(gòu)。例如, 如果要支持 ARMv7,就需要設(shè)置為“armeabi-v7a”。對(duì)于IA-32-x86和MIPS-mips這樣支持多體系架構(gòu)的系統(tǒng),應(yīng)該把 APP_ABI設(shè)置為“armeabi armeabi-v7a x86 mips”。在NDK修訂版本7或更高的版本中,可以簡(jiǎn)單的設(shè)置APP_ABI := “all rather enumerating all the architectures”。
- APP_PLATFORM — 為目標(biāo)平臺(tái)名稱;
- APP_STL — Android提供了一個(gè)最精簡(jiǎn)的libstdc c++運(yùn)行庫(kù),因此開(kāi)發(fā)人員使用的c++功能是非常有限的。然而使用APP_STL變量就可以使這些庫(kù)支持?jǐn)U展功能。
- NDK_TOOLCHAIN_VERSION-GCC — 選擇的GCC編譯器版本(默認(rèn)情況下設(shè)置為4.6)。
NDK-BUILDS
NDK-build是一個(gè)GNU Make的包裝容器。在NDK 4以后,ndk-build支持以下參數(shù):
- clean — 清除所有已生成的二進(jìn)制文件。
- NDK_DEBUG=1 — 生成可調(diào)式的代碼。
- NDK_LOG=1 — 顯示日志信息(用于調(diào)試)。
- NDK_HOST_32BIT=1 — 使Android系統(tǒng)支持64位版本(例如,NDK_PATH\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64,等等)。
- NDK_APPLICATION_MK=
— 指定Application.mk路徑。
在 NDK v5中,引入了NDK_DEBUG。當(dāng)NDK_DEBUG設(shè)置為“1”時(shí),便會(huì)生成可調(diào)試版本。如果沒(méi)有設(shè)置NDK_DEBUG,ndk-build會(huì)默 認(rèn)驗(yàn)證是否有在AndroidMainfest.xml文件中設(shè)置 android:debuggable=“true” 屬性。如果你使用的是NDK v8以后的版本,Google不建議你在AndoirdMainfest.xml文件中使用 android:debuggable 屬性(當(dāng)你使用“ant debug”或ADT插件生成調(diào)試版本時(shí),會(huì)自動(dòng)添加“NDK_DEBUG=1”)。
默認(rèn)情況下,設(shè)置了支持64位版本。你也可以通過(guò)設(shè)置“NDK_HOST_32BIT=1”強(qiáng)制使用一個(gè)32位的工具鏈來(lái)使用32位應(yīng)用程序。不過(guò),谷歌仍建議使用64位的應(yīng)用程序來(lái)提升大型程序的性能。
如何建立一個(gè)項(xiàng)目?
這 是個(gè)令人頭疼的步驟。你要安裝CDT插件并下載cygwin或mingw編譯器和Android NDK,在Eclipse設(shè)置里配置這些東西,但***還 是不能運(yùn)行。我***次開(kāi)始使用Android NDK時(shí),配置這些東西花了我3天時(shí)間。***發(fā)現(xiàn)問(wèn)題出在Cygwin編譯器身上:應(yīng)該為項(xiàng)目文件夾設(shè)置讀、寫(xiě)、可執(zhí)行的所有權(quán)限。
現(xiàn)在可就簡(jiǎn)單多咯!只需要照著這個(gè)鏈接到網(wǎng)址:http://developer.android.com/sdk/index.html 下載ADT包,這里面有開(kāi)始編譯環(huán)節(jié)需要用到的所有東西。
從Java代碼中調(diào)用本地方法
要從Java中調(diào)用本地代碼,首先你要在Java類中定義本地方法。例如:
- native String nativeGetStringFromFile(String path) throws IOException;
- native void nativeWriteByteArrayToFile(String path, byte[] b) throws IOException
你得在方法前使用“native”關(guān)鍵字。,這樣編譯器就知道這是JNI的入口點(diǎn)。這些方法會(huì)在C/C++文件中實(shí)現(xiàn)。Google建議用 “native+x”這樣的命名方式,“x”代表著方法的實(shí)際名稱。還有,在實(shí)現(xiàn)這些方法前你還得手動(dòng)生成一個(gè)頭文件。你可以手動(dòng)執(zhí)行此操作或者使用 JDK的 javah工具生成頭文件。然后讓我們將進(jìn)一步探討如何不用控制臺(tái),直接使用標(biāo)準(zhǔn)的Eclipse開(kāi)發(fā)環(huán)境:
- 打開(kāi)Eclipse,選擇Run -> External-tool-External -> External tools configurations。
- 新建配置。
- 指定javah.exe在jdk里的絕對(duì)路徑(例如,C:\Program Files (x86)\Java\jdk1.6.0_35\bin\javah.exe)。
- 在工作目錄中指定bin/class目錄的路徑(例如,?${workspace_loc:/NDKBegin/bin/classes}?)。
- 填入如下參數(shù):“-jni ${java_type_name}” (注意,輸入時(shí)不需要帶引號(hào))。
現(xiàn)在你可以運(yùn)行了。你的頭文件應(yīng)該放在bin/classes目錄下。下一步,復(fù)制這些文件到本地工程的jni目錄。打開(kāi)工程的配置菜單并選擇 Andorid Tools這一項(xiàng) — 添加本地庫(kù)(Add Native Library)。這樣我們就可以使用jni.h頭文件中包含的函數(shù)了。在此之后,你還要?jiǎng)?chuàng)建一個(gè).cpp的文件(有時(shí)候 Eclipse會(huì)默認(rèn)生成),并且方法實(shí)現(xiàn)已經(jīng)在頭文件中定義。
考慮到文章長(zhǎng)度和可讀性,我并沒(méi)有加入簡(jiǎn)單的代碼示例,所以你在這里找不到。如果需要,請(qǐng)?jiān)L問(wèn)這個(gè)鏈接https://github.com/viacheslavtitov/NDKBegining。
網(wǎng)頁(yè)題目:Android開(kāi)發(fā)進(jìn)階:AndroidNDK介紹
URL地址:http://www.dlmjj.cn/article/dphissh.html


咨詢
建站咨詢
