Android NDK 開發的初體驗

        Android SDK 開發程式都以 JAVA 語法為主,不過有些以 C/C++ 語言編寫的程式碼想要用 JAVA 來重寫,恐怕花的時間將很長。由於 Android 平台是從 C/C++ 所開發而成,因此 Android 提供一套 NDK (Native Development Kit) 讓原本 C/C++ 開發人員也能將 C++ 語言移植到 Android 環境裡。之前我們在 Linux 環境下編譯成功的 C++ 檔案,打算移植到 Android 平台上,初次使用 NDK 編譯遇上不少問題,過程中我們將這些問題記下來並找到解決辦法,希望對後進者提供幫助,縮短開發時間。

<遇上 NDK 編譯的問題>

問題一: NDK compiler 對於資料強制型態較嚴格,底下有兩個案例

         void *TimerManThread( void *arg )
         {
              printf("TimerManThread=%x\n", (unsigned int)arg);  /* 錯誤訊息 error: cast from 'void*' to 'unsigned int' loses precision [-fpermissive] */

              printf("TimerManThread=%x\n", (unsigned int*)arg);  /* 解決辦法 */ 
         }

         void AChkPtr_(void *ptr)
         {
               unsigned ptrx = (unsigned) ptr; /* 錯誤訊息 */
               unsigned *ptrx = (unsigned *) ptr;  /* 解決辦法 */ 
         }

問題二: 與資料轉換或數字檢查相關,NDK 編譯所產生的錯誤訊息,如下。
      error: 'toupper' was not declared in this scope
      error: 'tolower' was not declared in this scope
      error: 'isdigit' was not declared in this scope
      error: 'isspace' was not declared in this scope

        因為沒有把這些函式的定義 include 進來,解決辦法就是在檔案前面增加 #include <ctype.h> 即可。

問題三: NDK 編譯錯誤訊息 error: 'pthread_cancel' was not declared in this scope 

        因為不支援 pthread_cancel( ) 函式,解決辦法就是改使用 pthread_kill( ) 函式。
        原本 pthread_cancel( m_Task );
        改成 pthread_kill( m_Task, SIGKILL );

問題四: NDK 編譯錯誤訊息 error: 'S_IWUSR' was not declared in this scope

        解決辦法就是在檔案前面增加 #include <sys/stat.h> 即可。

問題五: 在 Cyassl 的資料定義中,沒有定義到 64-bit 資料
      Cyassl/ctaocrypt/include/types.h:71:13: error: unknown type name 'word64'

        解決辦法就是新增 typedef long long unsigned int word;

問題六: 同一個函式庫,但在編譯 BUILD_STATIC_LIBRARY 和 BUILD_SHARED_LIBRARY 卻有不同的結果。BUILD_SHARED_LIBRARY 會出現 error: undefined reference to ...

       解決辦法是找出參照的函式在哪個函式庫裡面,然後再將此函式庫名放到底下的變數裡。LOCAL_SHARED_LIBRARIES := libAltiTool libSip libExpat libSrtp
這裡的 libAltiTool libSip .... 都是被參照到的函式庫。


<整合 NDK 與 Eclipse 環境>

        接下來,我們要確定 NDK 編譯的函式庫能與 Eclipse 環境整合在一起,所以拿 Android 官網上面的一個 hello-jni 範例來測試。第一步,創建一個 hello-jni Android Project,如圖一所示。第二步,在 project 底下新建資料夾,名為 jni。再把官網上的 jni 範例複製過來,總共有三個檔案。依照圖二 ~ 圖五的步驟設定 NDK 編譯環境,試試看 NDK 有沒有成功編譯 hello-jni.c 並產生一個 libhello-jni.so 函式庫。若能成功編譯表示環境設定正確,最後在 AVD 虛擬機上面執行 JNI 的結果,如圖六所示。

圖一:新創一個 hello-jni project

圖二:新建一個 NDK 編譯環境

圖三:設定 NDK 編譯工具的位置

圖四:設定編譯後的函式庫更新位置

圖五:設定編譯的條件以及編譯的資料夾

圖六:AVD測試的結果

<用 NDK 方式存取檔案>

        在 C 環境底下開啟一個檔案,我們經常呼叫 fopen 函式,但是在 NDK 平台該如何操作呢?第一步,我們寫一個 fopen 檔案開啟模式,如圖七所示。要注意的是檔案存放的位置,手機可不像電腦硬碟,允許使用者愛怎麼放就怎麼放,因此遵照 Android 平台的規定我們將檔案放在 /sdcard 目錄底下。

圖七:寫一個 fopen 的檔案開啟模式

        第二步,我們要開啟 APP 存取的權限。如果沒有打開手機存取的權限,APP 不允許寫入檔案或創建新檔案,即使只是讀取檔案也必須打開權限才行。我們要在 APP 的 manifest 新增一條權限,如圖八所示,這樣才能在手機存取檔案。

圖八:開啟 APP 的存取權限

        最後,我們將編譯完成的 APP 載入手機上測試,可以從 debug 視窗看到我們寫在代碼的 log,如圖九所示。這個測試是在手機上才能測試,如果用虛擬機器是無法測試出來的。

圖九:在手機上測試的結果


參考文章
[1] Android 開發筆記 - 使用 Android NDK (Native Development Kit) 
[2] Android NDK 讓C/C++語言寫的程式也能在Android上執行 https://magiclen.org/android-ndk/
[3] Android NDK 測試 Hello 專案 http://pclevin.blogspot.tw/2014/12/android-ndk-hello.html
[5] SIP Voip 在 Android 系統的開發 https://han-ya.blogspot.tw/2016/07/sip-voip-android.html

這個網誌中的熱門文章

Android APP 藍芽範例說明 -- BluetoothChat

Android APP BLE範例程式 -- BluetoothLeGatt

三軸重力加速計 Accelerometer