[Android] 安裝NDK與使用JNI呼叫系統底層native的C/C++程式 (Java call C)

因為工作需要,所以在這做個筆記
也順便學習一下

JNI全名叫Java Native Interface,意思就是說
這不是Android獨有的東西,純Java的應用程式也可以寫
(但本例還是以Android下的JNI為主)

以下是用Mac去搭建的,同樣的做法Linux也可以

 

環境準備

假設你已經有開發過Android的經驗

有Android  SDK+Eclipse最基礎的環境

這樣你需要加裝NDK(Native Development Kit) 和CDT(C/C++ Development Toolkit)

首先,先到Android官網下載NDK,解壓縮後存在一個路徑

下載網址:

http://developer.android.com/tools/sdk/ndk/index.html

像本例用Mac搭建,所以下載Mac版本

 

在Help > Install New Software 就像是你之前在安裝Android的環境一樣

Screen Shot 2013-01-15 at 5.54.38 PM  

但我們是要安裝CDT,這裡小小的不一樣

Work with:這欄打入

http://download.eclipse.org/tools/cdt/releases/indigo

然後勾選CDT Main Features安裝C/C++開發環境

Screen Shot 2013-01-15 at 6.03.51 PM  

接下來就是Next大法,沒啥好解釋的

Screen Shot 2013-01-15 at 6.04.54 PM  Screen Shot 2013-01-15 at 6.05.11 PM  

按下Finish,馬上就開始下載了

Screen Shot 2013-01-15 at 6.06.12 PM  

安裝完成的訊息問你要不要重開Eclipse,就照預設值Restart Now

Screen Shot 2013-01-15 at 6.11.25 PM  

但還沒完,

Eclipse重開了以後,再次Install New Software回到這個畫面

這次填上ADT的網址

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

這次勾選NDK Plugins

Screen Shot 2013-01-15 at 6.14.40 PM  

剩下那些廢圖我就不重覆貼了

CDT和NDK安裝只多了一個警示框,按OK繼續安裝

Screen Shot 2013-01-15 at 6.17.38 PM  

Eclipse再次重開之後,在Eclipse > Preferences視窗中 

Screen Shot 2013-01-16 at 8.55.38 AM   

Android > NDK的頁籤中

選取你下載解壓後的NDK路徑
我是把它跟android sdk放在一起,有必要的話可以參考我的路徑

 Screen Shot 2013-01-16 at 8.55.24 AM  

環境設定完,就馬上開始寫程式摟!

新增專案

就像是老樣子,New > Android Project,開啟一個Android專案
因為這裡講到爛掉,所以我跳快一點

Screen Shot 2013-01-16 at 9.03.08 AM  

我開了一個專案

Application Name: HelloNDK
Package Name: com.J_Test.hellondk

Activity Name: MainActivity
Layout Name: activity_main

加入JNI支援

這裡就跟一般的Android專案不一樣

在你的專案按下右鍵 > Android Tools > Add Native Support…

Screen Shot 2013-01-16 at 9.07.37 AM  

出現一個視窗
提示你JNI編譯輸出的名稱

Screen Shot 2013-01-16 at 9.13.00 AM  

按下Finish之後,會發現你的專案目錄多了Includes和jni資料夾這二項

Screen Shot 2013-01-16 at 9.20.29 AM

 

設計JNI函數名稱 

HelloNDK.cpp和Android.mk是我們今天編輯的重點

首先,先將HelloNDK.cpp 重新命名為 HelloNDK.c

然後編輯Android.mk

LOCAL_PATH := $(call my-dir)

 

include$(CLEAR_VARS)

 

LOCAL_MODULE    := HelloNDK

LOCAL_SRC_FILES := HelloNDK.c

 

include$(BUILD_SHARED_LIBRARY)

這個HelloNDK和HelloNDK.cpp,就是先前填寫.so的名稱 
將檔名改成對應的名稱

 

我們在MainActivity加入以下:

static

{

     System.loadLibrary("HelloNDK");

} 

public native String helloString();

public native int plus(int a, int b);

public native int multiply(int a, int b);

 

把我們要呼叫C使用的方法界面,加個native修飾字
此例

helloString()  回傳一個測試字串

int plus(int a, int b)  做兩數相加

int multiply(int a, int b)  做兩數相乘

最後加上System.loadLibrary 載入我們即將要自定的JNI Module Name

 

產生Header檔案

我們用javah指令產生JNI對應的Header file
但它會讀取編譯後的*.class檔案

所以在這之前,請將專案整個Build過一遍

請先確定專案下的bin/classes+package名稱的對應之資料夾
是否有編譯後的*.class檔(這在Eclipse專案目錄下不會看見,用Finder去開)

本例是HelloNDK/bin/classes/com/J_Test/hellondk/MainActivity.class

 

接下來,打開Terminal

切換到專案的根目錄

使用這個指令

javah -d jni -classpath bin/classes <package名稱+class名稱

在本例就是

javah -d jni -classpath bin/classes com.J_Test.hellondk.MainActivity 

順利執行之後,回到Eclipse,專案視窗按F5重新整理

在jni資料夾會多出com_J_Test_hellondk_MainActivity.h這個檔,內容為以下

/* DO NOT EDIT THIS FILE – it is machine generated */

#include<jni.h>

/* Header for class com_J_Test_hellondk_MainActivity */

 

#ifndef _Included_com_J_Test_hellondk_MainActivity

#define _Included_com_J_Test_hellondk_MainActivity

#ifdef __cplusplus

extern“C” {

#endif

/*

 * Class:     com_J_Test_hellondk_MainActivity

 * Method:    helloString

 * Signature: ()Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_com_J_1Test_hellondk_MainActivity_helloString

  (JNIEnv *, jobject);

 

/*

 * Class:     com_J_Test_hellondk_MainActivity

 * Method:    multiply

 * Signature: (II)I

 */

JNIEXPORT jint JNICALL Java_com_J_1Test_hellondk_MainActivity_multiply

  (JNIEnv *, jobject, jint, jint);

 

#ifdef __cplusplus

}

#endif

#endif

好吧,我承認它是真的醜了點
但還是可以寫C程式的 

想想當初怎麼寫C程式,一個標頭檔…只宣告參數型別
但沒宣告參數名稱

 

實作JNI

編輯HelloNDK.c內容

#include<jni.h>

#include<string.h>

 

#include“com_J_Test_hellondk_MainActivity.h”

 

JNIEXPORT jstring JNICALL Java_com_J_1Test_hellondk_MainActivity_helloString(

JNIEnv *env, jobject thiz) {

   return (*env)->NewStringUTF(env, “Hello from JNI !”);

}

 

JNIEXPORT jint JNICALL Java_com_J_1Test_hellondk_MainActivity_multiply(

JNIEnv *env, jobject thiz, jint a, jint b) {

   jint total = 0;

   total = a * b;

   return total;

}

先不管JNIEXPORTJNICALL等關鍵字

jintjobjectjstring等型別,這些是Java裡給的型別

這裡有張對照表

Java型別 型別表示 字節大小(bit)
boolean jboolean 8, unsigned
byte jbyte 8
char jchar 16, unsigned
short jshort 16
int jint 32
long jlong 64
float jfloat 32
double jdouble 64
void void

至於String、物件、陣列…等,處理起來就較複雜些


疑難排解

在編譯時我有遇到

Method ‘NewStringUTF’ could not be resolved 

 

解決辦法,

在專案下按右鍵  Properties >C/C++ General > Paths and Symbols 的頁籤中

按下Add…選擇

<ndk路徑>/platforms/android-14/arch-arm/usr/include

/Users/johnny/android-sdks/android-ndk/platforms/android-14/arch-arm/usr/include

就可以解決

Screen Shot 2013-01-17 at 2.58.43 AM  

若在使用javah遇到類似的訊息

error: cannot access com.J_Test.hellondk.MainActivity
class file for com.J_Test.hellondk.MainActivity not found
javadoc: error – Class com.J_Test.hellondk.MainActivity not found.
Error: No classes were specified on the command line. Try -help.

請先確定*.class的位置

不含package的資料夾路徑

要從專案裡的bin資料夾,或是bin/classes資料夾尋找

若沒有,請重新編譯一次(輸出一次到模擬器是個不錯的選擇)


最後,感謝這些參考資料,都是來自世界各個角落的精華
讓我好好學了一課!

device-2013-01-17-023803  

對了,有了function name,要寫個測試程式應該不難吧
這種艱巨的任務,就交給你了!(笑)

 

 

參考資料: 

http://androidcookbook.com/Recipe.seam?recipeId=77

http://changyy.pixnet.net/blog/post/29469121
http://changyy.pixnet.net/blog/post/29437517

http://developer.android.com/training/articles/perf-jni.html

http://87showmin.blogspot.tw/2009/06/java-java-native-interfacejni.html

https://github.com/androidcook/Android-Cookbook-Examples/tree/master/NdkDemo

http://java.chinaitlab.com/JDK/36678.html

http://blog.csdn.net/ljlsunny/article/details/5753006

http://electrofriends.com/qna/jni-faq/convert-jstring-cstyle-string-vice-versa/

http://blog.csdn.net/gavinr/article/details/7343324

在〈[Android] 安裝NDK與使用JNI呼叫系統底層native的C/C++程式 (Java call C)〉中有 5 則留言

  1. 你好,請問版主還有在經營嗎!?
    我想請教問題,我想執行這個網站的程式http://www.cs.cornell.edu/courses/cs4670/2010fa/projects/final/results/group_of_acc269_ty244_yc563/cs4670_final.html (網頁沒有問題,請放心)
    他給的source code是需要用NDK編譯OpenCV的,但是我一直沒辦法把環境架起來,請問您能否教一下如何處理這問題?
    勞煩之處,非常感謝。謝謝。
    版主回覆:(07/09/2013 09:02:17 AM)
    你用Windows / Mac / Linux ?

  2. 我是使用windows系統,謝謝
    版主回覆:(08/14/2013 02:54:20 PM)
    因為OpenCV我也沒用過,感覺是個很有趣的東西:)
    以NDK來說…
    除了文章提到的NDK和Eclipse的插件
    Windows的話,還要加裝cygwin
    安裝詳情我就不清楚了,因為我一路也都用mac系統
    有空我裝看看Windows版的
    如果真的在Windows上架不起來
    不彷在Windows加裝個虛擬機,裝台Linux虛擬機吧:)

  3. 版主,之前我有讀過你的一篇HTTP與POST的文章,兩篇我都讀過了
    與本機的資料庫連線沒有問題,
    但是最近因為移到網路的伺服器端,要連結遠端的伺服器
    就出現錯誤了,
    我移除了判斷if (httpResponse.getStatusLine().getStatusCode() == 200)
    因為沒有移除,就直接拋出錯誤了..
    我直接回傳strResult
    顯示的錯誤碼如下
    406 Not Acceptable
    This request is not acceptable
    Powered By LiteSpeed Web Server LiteSpeed Technologies is not responsible for administration and contents of this web site!
    求幫助…
    版主回覆:(08/14/2013 02:38:09 PM)
    這個if就是判斷是否正確的回傳我們要的網頁(HTTP 200 OK)
    現在你的HTTP代碼為406 Not Acceptable
    有些機器是HTTP 500 Internal Server Error
    或是最常見的404 Not Found
    這都是要處理的
    你的406問題我覺得這個問題應該是出在Server

  4. 版主你好,我按照你的步驟做,遇到了兩個問題:
    1.在連接http://dl-ssl.google.com/android/eclipse/ 時並沒有出現 NDK plugin沒有出現
    2.在執行 javah -d jni -classpath …… 時 它顯示以下錯誤:
    Error: cannot access android.app.Activity
    class file for android.app.Activity not found
    版主回覆:(08/13/2014 01:59:11 AM)
    1. 它的選項改到Development tools裡了
    Development tools > Android Native Development tools
    2. 看起來可能是路徑有錯
    javah -d jni -classpath bin/classes com.J_Test.hellondk.MainActivity
    它搜尋的資料夾是bin/classes資料夾
    PackageName+ClassName為 com.J_Test.hellondk.MainActivity
    所以要下這指令的所在目錄要在專案最上層的目錄(與AndroidManifest.xml同一層)
    假設專案資料夾名稱為hellondk
    所以Terminal所在的目錄應該為
    /..(您workspace的路徑)…/hellondk/
    而不是以下這些
    /..(您workspace的路徑)…/hellondk/bin/classes/com/J_Test/hellondk/
    /..(您workspace的路徑)…/hellondk/bin/classes/
    /..(您workspace的路徑)…/hellondk/bin/
    可以用pwd指令查看當前目錄

留言功能已關閉。