· 8步教你打開Android之門 NDK入門教程 android
這是一篇Android NDK開發的入門教程,在這一教程結束後,你將建立你本身的項目,從Java代碼簡單地調用原生C語言代碼。 算法
教程細節 架構
技術:Android SDK、NDK、C 語言 函數
難度:進階 工具
預計完成時間:60-90 分鐘 網站
先決經驗 ui
在咱們開始以前,咱們須要先花點時間瞭解一下這一教程的難度。它的標記是「進階」。之因此標爲「進階」是由於咱們這些做者想要確保你符合如下要求: this
你有Java和C語言經驗。 spa
你能適應命令行操做。 操作系統
你知道如何瞭解你的 Cygwin、awk 和其餘工具的版本。
你能適應 Android Development。
你有一個有效的 Android 開發環境(本文撰寫時,筆者使用的是 Android 2.2)
你使用 Eclipse 或者能夠將 Eclipse 的指導步驟輕鬆應用於你本身的 IDE 上。
就算你並不知足這些條件,咱們固然也歡迎你閱讀這一教程,不過你可能在某些步驟遇到困難,若是你知足了以上條件這些困難就會輕易解除。也就是說,即便你認爲本身是個移動開發老手,使用 NDK 依然很容易碰到困難和麻煩。請注意你可能要自行排查故障才能讓一切正常運轉於你的開發系統中。
本教程提供完整的樣例項目的開源代碼下載。
什麼時候使用 NDK 的說明
好,若是你正在閱讀這篇教程,你也許已經在考慮在你的 Android 項目中使用 NDK 了。不過,咱們想要花點時間討論一下 NDK 爲何那麼重要、什麼時候該使用它,以及——同等重要的,什麼時候不應使用它。
總的來講,只有當你的應用程序真的是個處理器殺手的時候你才須要使用 NDK。也就是說,你設計的算法要利用 DalvikVM 中全部的處理器資源,並且原生運行較爲有利。還有,別忘了在 Android 2.2 中,JIT 編譯器會提升相似代碼的效率。
另外一個使用 NDK 的緣由是方便移植。若是你在現有的應用程序中有大量的 C 語言代碼,那麼使用 NDK 不只能夠加速你的項目的開發進程,也能在你的 Android 和非 Android 項目中保持修改的同步。這一點對於那些爲其餘平臺而寫的 OpenGL ES 應用程序來講尤其如此。
別覺得只要用了原生代碼就能提升你的應用程序的效率。Java 與原生 C 語言之間的轉換會增長一些資源開銷,所以只有你有一些集中消耗處理器資源的任務時才真正有必要這麼作。
第 0 步:下載工具
好了,讓咱們開始吧。你須要下載 NDK。咱們先開始下載,由於在下載的過程當中你能夠檢查一下確保你所須要用到的其他工具的版本都正確。
從 Android 網站下載適合你的操做系統的 NDK。
如今,對照下列檢查你的工具版本:
若是在 Windows 下,Cygwin 1.7 或更高版本
將 awk 升級到最新版本(咱們使用的是 20070501)
GNU Make 3.81 或更高版本(咱們使用的是 3.81)
若是其中任何一個的版本太舊,請在繼續以前先升級。
第 1 步:安裝 NDK
既然 NDK 已經下載完成(沒錯吧?),你就須要解壓縮它。解壓後將它放入合適的目錄中。咱們把它放在和 Android SDK 相同的目錄下。記住你把它放在哪裏了。
如今,你也許想要在路徑設置中添加 NDK 工具。若是你在 Mac 或 Linux 下,你能夠用你的原生路徑設置來完成。若是你在 Windows 下的 Cygwin,你就須要設置 Cygwin 的路徑設置。
第 2 步:建立項目
建立一個常規的 Android 項目。爲了不往後的問題,你的項目的路徑必須不包含空格。咱們的項目有個叫作「com.mamlambo.sample.ndk1」的包,帶有一個叫作「AndroidNDK1SampleActivity」的默認 Activity——你以後還會看到它們。
在這個項目的頂層建立一個叫作「jni」的目錄——這是你放置原生代碼的地方。若是你很熟悉 JNI,那你就會知道 Android NDK 很大程度上基於 JNI 的概念——它本質上是個只有有限的 C 語言編譯頭文件的 JNI。
第 3 步:添加一些 C 語言代碼
如今,在 jni 文件夾中,建立一個叫作 native.c 的文件。一開始將如下 C 語言代碼寫入該文件,咱們之後再添加另外一個函數:
1. #include
2.
3. #include
4.
5. #include
6.
7. #define DEBUG_TAG "NDK_AndroidNDK1SampleActivity"
8.
9. void Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_helloLog(JNIEnv * env, jobject this, jstring logThis)
10.
11. {
12.
13. jboolean isCopy;
14.
15. const char * szLogThis = (*env)->GetStringUTFChars(env, logThis, &isCopy);
16.
17. __android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK:LC: [%s]", szLogThis);
18.
19. (*env)->ReleaseStringUTFChars(env, logThis, szLogThis);
20.
21. }
22.
這個函數實際上很是淺顯。它獲取一個 Java 對象的字符串參數,將它轉換爲 C-string,而後將它寫入到 LogCat 中。
不過該函數的名字很重要。它遵循了以「Java」的特定字樣開頭,後面跟着包名稱,而後類名稱,而後方法名稱,和 Java 中定義的同樣。每一部分都由一根下劃線隔開,而不是點。
該函數的頭兩個參數也很重要。第一個參數是 JNI 環境,它與 helper 函數會被頻繁調用。第二個參數是該函數所屬的 Java 對象。
第 4 步:從 Java 中調用原生代碼
既然你已經寫好了原生代碼,讓咱們回頭看看 Java 這邊。在默認的 Activity 中,按照你的喜愛建立一個按鈕,並添加一個按鈕處理器。從按鈕處理器中,對 helloLog 做調用:
23. helloLog("This will log to LogCat via the native call.");
而後你必須在 Java 這邊添加函數聲明。在你的 Activity 類中添加以下聲明:
24. private native void helloLog(String logThis);
它告訴編譯和連接系統該方法將在原生代碼中實現。
最後,你須要加載原生代碼最終編譯到的庫。在 Activity 類中添加以下的靜態初始化程序來根據名稱加載庫(庫的名字隨你決定,在下一步還會用到):
25.
26.
27. static {
28.
29. System.loadLibrary("ndk1");
30.
31. }
32.
第 5 步:添加原生代碼的 Make 文件
在 jni 文件夾中,如今你須要添加在編譯中要用到的 makefile。該文件必須以「Android.mk」命名,若是你以前命名的文件爲 native.c,庫爲 ndk1,那麼 Android.mk 的內容就應該是這樣:
33.
34.
35. LOCAL_PATH := $(call my-dir)
36.
37.
38.
39. include $(CLEAR_VARS)
40.
41.
42.
43. LOCAL_LDLIBS := -llog
44.
45.
46.
47. LOCAL_MODULE := ndk1
48.
49. LOCAL_SRC_FILES := native.c
50.
51.
52.
53. include $(BUILD_SHARED_LIBRARY)
54.
第 6 步:編譯原生代碼
既然你的原生代碼已完成,make 文件也已就緒,是時候編譯原生代碼了。在命令行下(Windows 用戶在 Cygwin 下),你須要在你的項目的根目錄下運行 ndk-build 命令。ndk-build 工具就在 NDK 工具目錄中。將它添加到咱們的路徑中是最方便的辦法。
在以後的編譯中,若是你使用「ndk-build clean」命令,那麼你能夠確保全部的東西都被從新編譯了。
第 7 步:運行代碼
如今你已準備穩當能夠運行代碼了。在你最喜歡的模擬器或者手持設備中載入該項目,查看 LogCat,而後點擊按鈕。
可能有兩件事情會發生。首先,它可能正常工做了。這樣的話,恭喜你!不過你可能仍是想要繼續閱讀下去。你也可能在 LogCat 中獲得相似「Could not execute method of activity」這樣的錯誤。這很正常。這只是說明你漏掉了某個步驟罷了。用 Eclipse 很容易發生這種狀況。一般,Eclipse 被設置爲自動重編譯。若是它不知道有東西被修改了,它就不會自動重編譯和重連接。在本例中,Eclipse 不知道你編譯了原生代碼。因此,「清除(cleaning)」該項目(在 Eclipse 工具欄中點擊項目(Project)->清除(Clean)),強制 Eclipse 重編譯。
第 8 步:添加另外一個原生函數
接下來的函數將不只演示返回值的能力,還會演示返回例如字符串這樣的對象的能力。在 native.c 中添加以下函數:
1. jstring Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_getString(JNIEnv * env, jobject this, jint value1, jint value2)
2. {
3. char *szFormat = "The sum of the two numbers is: %i";
4. char *szResult;
5. // add the two values
6. jlong sum = value1+value2;
7. // malloc room for the resulting string
8. szResult = malloc(sizeof(szFormat) + 20);
9. // standard sprintf
10. sprintf(szResult, szFormat, sum);
11. // get an object string
12. jstring result = (*env)->NewStringUTF(env, szResult);
13. // cleanup
14. free(szResult);
15. return result;
16. }
17.
18.
爲了正常編譯,你會須要添加一個 include stdio.h 的聲明。並且,爲了響應這個新的原生函數,請在你的 Activity Java 類中添加以下聲明:
19.
20. private native String getString(int value1, int value2);
你如今能夠隨意設定其功能。咱們使用以下兩個調用和輸出:
21. String result = getString(5,2);
22. Log.v(DEBUG_TAG, "Result: "+result);
23. result = getString(105, 1232);
24. Log.v(DEBUG_TAG, "Result2: "+result);
回到 C 語言函數中,你會注意到咱們作了許多事情。首先,咱們在使用 malloc() 函數中的 sprintf() 調用時須要建立一個緩衝器(buffer)。若是你不會忘記經過使用 free() 函數清理結果,那麼這就很合理了。而後,爲了傳回結果,你可使用一個叫做 NewStringUTF() 的 JNI helper 函數。該函數基本上就是獲取一個 C 語言字符串,以之建立一個新的 Java 對象。這個新的字符串對象就能夠在以後做爲結果返回,你就能夠在 Java 類中將它做爲一個常規 Java 字符串對象使用了。
指令集、兼容性,等等
Android NDK 須要 Android SDK 1.5 或更高版本。在新版本的 NDK 中,有些新的頭文件可用於擴大對某些 API 的訪問——特別是 OpenGL ES 庫。
不過,那些都不是咱們要談論的兼容性。這是原生代碼,在使用時由處理器構架編譯。所以,你要問本身的一個問題會是它支持何種處理器構架?在目前的 NDK 中(在本文撰寫時)它只支持 ARMv5TE 和 ARMv7-A 指令集。默認設置下,目標架構被設置爲 ARMv5TE,它能夠在使用 ARM 芯片的 Android 設備上運行。
它預計將來將支持其餘指令集(其中提到了 x86)。這其中有頗有意思的潛在情況:NDK 解決方案沒法適用於全部的設備。例如,市面上有使用 x86 指令集的英特爾(Intel)Atom 處理器的 Android 平板設備。
那麼 NDK 在模擬器上如何呢?模擬器運行的是真正的虛擬機,包括完整的處理器虛擬。沒錯,這意味着在虛擬機中運行 Java 就等因而在虛擬機中運行了一個虛擬機。