Android 如何藉由JNI來使用C/C++程式?


雖然Google官方推薦使用基於Java程式語言Android SDK來開發Android App,但是Android SDK卻沒有辦法完全地開發出能在Android系統上執行的所有功能,而且也因為Java語言編譯出來的Bytecode在Runtime時需要再次直譯成機械碼,因此Android SDK效能並沒有說很好。還好Android有提供NDK,可以使用JavaJNI(Java Native Interface),讓AndroidJava程式也能夠使用C/C++的Native Code。

開發環境

首先是IDE(Integrated Development Environment)的選擇,大部分的人都是使用Eclipse,所以能查到的資料也比較多,故在這裡也是使用Eclipse。為了使Eclipse能夠同時開發AndroidC/C++,必須要替Eclipse安裝ADT(Android Development Tools)與CDT(C/C++ Development Tooling)插件。要讓Android能夠使用JNI,還要再安裝Android Native Development Tools,這樣Eclipse才可以支援Android NDK。當然,Android NDK也是需要的。

安裝ADTAndroid Native Development Tools

Eclipse的「Install New Software」中,使用以下來源網址,就可以找到Android的開發工具。

https://dl-ssl.google.com/android/eclipse/

Android 如何藉由JNI來使用C/C++程式?

安裝CDT

EclipseCDT網頁,就可以下載到符合自己Eclipse版本的CDT,當然也可以像ADT一樣使用Eclipse的「Install New Software」來安裝。

http://www.eclipse.org/cdt/

Android 如何藉由JNI來使用C/C++程式?

下載與設定Android NDK

下載Android NDK請參考這篇文章。下載並解壓縮後,要讓Eclipse知道Android NDK的位置,因此要在Eclipse的「Preferences」中,在「Android」分類下找到「NDK」,並指定NDK的根目錄路徑。

Android 如何藉由JNI來使用C/C++程式?

Android專案支援JNI

在Android專案項目上按下滑鼠右鍵,出現選單後,選擇「Android Tools」->「Add Native Support」。

Android 如何藉由JNI來使用C/C++程式?

接著輸入函式庫的名稱,就是.so檔案的名稱。在一個Android專案下透過JNI可以使用多個不同的.so檔案,.so檔案的命名可以幫助設計師了解到這個檔案的用途為何。

Android 如何藉由JNI來使用C/C++程式?

替專案加入Native Support後,專案下會多了一個「jni」目錄,這個目錄便是C/C++程式的工作目錄,所有C/C++程式,或是相關的資源都可以放進這個目錄裡面。

Android 如何藉由JNI來使用C/C++程式?

JNI的使用方法

使用javah建立C/C++的標頭檔(*.h)

建置好支援JNIAndroid專案後,先別急著開始寫C/C++程式,應該要先規劃好Java程式中哪些地方需要使用到由C/C++程式語言實作的方法或是函數,這些方法都需要先使用native修飾子來進行宣告,並且使用System類別下的loadLibrary方法來讀入指定名稱的.so函式庫。用以下程式為例:

接著再使用JDK提供的「javah」工具,來將指定類別轉換成JNI使用的標頭檔。使用終端機,將工作目錄移動到Android專案的「src」目錄下,接著使用以下指令來產生.h檔案:

javah 包含Package名稱的完整類別名稱

例如:

javah org.magiclen.androidjni.MainActivity

Android 如何藉由JNI來使用C/C++程式?

執行成功後,會在目前的工作目錄下產生.h檔案。檔名的話,會將完整類別名稱的「.」改為「_」。

Android 如何藉由JNI來使用C/C++程式?

使用.h標頭檔來實作程式

將使用javah產生出來的.h檔案移動到「jni」目錄下,然後在別的.c或是.cpp檔案中include,並實作出其中宣告的函數。程式如下:

編譯JNIC/C++程式

雖然Eclipse應該會在編譯Android SDK時,自動使用NDK來編譯JNI下的C/C++程式,但是十分的不可靠,常常會遇到奇怪的問題導致專案一直編譯失敗。如果要編譯JNIC/C++建議還是在終端機下動作。Android NDK提供「ndk-build」工具,位於NDK根目錄下,可以快速地編譯Android專案下的C/C++程式,而且使用方式很簡單,只要將終端機的工作目錄移動到Android NDK下,再執行以下指令即可開始編譯:

ndk-build

Android 如何藉由JNI來使用C/C++程式?

ndk-build跟makefile類似,不會編譯已經編過且原始碼未被改變的部份,如果想要清空先前的編譯結果,可以使用以下指令來清除:

ndk-build clean

Android 如何藉由JNI來使用C/C++程式?

使用NDK編出.so檔之後,就可以使用Android SDK來編譯出Android App。執行結果如下:

Android 如何藉由JNI來使用C/C++程式?

替不同平台編譯出不同的.so檔

預設的情況下,NDK只會編譯出armeabi架構的.so檔案,可以用在任何ARM架構的Android裝置上。如果要針對ARMv7架構優化,或是使.so檔能在x86架構或是MIPS架構上執行的話,可以在「jni」目錄下,增加「Application.mk」檔案,並且設定「APP_ABI」參數的值。

例如要編譯出ARM與x86的.so檔案,可以這樣設定:

APP_ABI := armeabi x86

如果要直接支援所有的架構,可以這樣設定:

APP_ABI := all

有些程式碼可能會不太能在MIPS上執行,寫成以下這樣子會比較保險:

APP_ABI := armeabi armeabi-v7a x86

接著再使用「ndk-build」工具來編譯,就可以自動編出不同架構下的.so檔案了。

Android 如何藉由JNI來使用C/C++程式?

JNI下的Java資料型別

JNI下的C/C++程式如果要使用Java的資料型別,型別名稱前必須要加上「j」,例如:「int」變成「jint」,「long long」變成「jlong」,「String」變成「jstring」,都是小寫字母。這裡要注意的是,如果是物件型別(非基本資料型別)的話,只有特定幾個物件可以直接加上「j」,像是「String」或是「Array」,不然的話都是「jobject」。

若要呼叫jobject的方法要怎麼辦呢?可以使用JNIEnv的GetMethodID來取得方法的ID,再依照方法的回傳值型態來決定使用JNIEnv的哪種call。以下程式,使用jni呼叫SDK上textView.setText(String)的功能。

NDKJNI用起來不簡單,而且很容易造成記憶體洩漏(Memory Leak),以及程式閃退(出現Error,而不是歡樂的Exception)的狀況,使用前還是建議先詳讀Google官方提供的NDK文件。

關於作者

Magic Len

各位好,我是Magic Len,是這網站的管理員。我是台灣台中大肚山上人,畢業於台中高工資訊科和台灣科技大學資訊工程系,曾在桃機航警局服役。我熱愛自然也熱愛科學,喜歡和別人分享自己的知識與經驗。如果你有興趣認識我,可以加我的Facebook,並且請註明是從MagicLen來的。

相關文章