這篇文章來說說ndk的使用方法,其實主要是關於ndk的一些編譯選項的研究和翻譯(其實人家google的文檔已經說的很清楚了)。偶選用的測試環境是 slackware 12.0 + android 1.5 r1 for linux + jdk 1.6.0_12,ndk選用的是android 1.5 ndk r1這個版本的(直接解壓就行,免安裝的)。
1、從ndk安裝說起
ndk安裝的時候需要運行一 個~/android-ndk-1.5_r1/build/目錄下面的一個叫做host-setup.sh的指令碼。大略讀了一下這個指令碼,發現這個主要是 用來產生out/host/host/config.mk檔案的。主要用於指定使用者作業系統的判斷以及支援的編譯器類型(設定makefile中的 cc,ar,ld之類的變數)
ndk的目錄介紹。
2、ndk的目錄結構分析
進入android-ndk-1.5_r1目錄,看到如下目錄結構:
GNUmakefile: 標準的makefile格式的檔案,用於引用build/core/main.mk的編譯指令碼。
README.TXT:基本的說明,沒啥大用,真正有用的文檔都在docs目錄下面。
apps/:存放帶有jni介面的android工程目錄(工程裡面有利用native關鍵字定義的java函數)
build/:存放著幾乎所有的ndk編譯相關的指令碼以及必要的靜態連結庫。
docs/:存放這ndk的所有“官方”文檔,每一篇文檔對於jni編寫者來說這裡面的任何一點點資料都是無價的。
out/:存放一些中間的臨時檔案,例如jni的.c/.cpp檔案編譯過程中產生的.o檔案等。
sources/:存放jni檔案的.c/.cpp的原始碼檔案。
3、基本的使用方法
(1)建立一個android工程
進入apps目錄,運行如下命令:
android create project --target 2 --package com.TWM --activity NDKTest --path ./NDKTest/project
通 過命令列建立一個叫做NDKTest的activity,注意這裡的--path需要設定為./XXXXX/project這個目錄,這個XXXXX目錄 主要是為了ndk的make區分不同項目和工程使用的。編寫Application.mk檔案的時候,一定要把Application.mk寫到這個 XXXXX目錄下面。
$NDK/apps/<myapp>/Application.mk
另外,編譯jni庫的時候使用的命令也是如此:
make APP=<your app name>
這裡的<your app name>實際上也是這個XXXXX目錄。
(2)為工程添加一個jni的java調用介面
進入app/NDKTest/project/src/com/TWM/NdkTest目錄,建立一個新的java檔案(例如:NDKJni.java),然後把代碼寫成類似下面這個樣子:
package com.TWM.NdkTest ;
public class NDKJni {
public native int MyFunc(int a, int b) ;
static {
System.loadLibrary("NDKjni") ;
}
}
這裡的MyFunc由於是使用native修飾,因此,這個MyFunc函數是一個調用jni的函數。
(3)為java工程編寫Application.mk檔案
該檔案主要放在app/NDKTest目錄下,用於告知ndk的編譯指令碼,當前的程式需要哪個jni模組。
看上去應該是這個樣子的:
APP_PROJECT_PATH := $(call my-dir)/project ---> 目前的目錄下的project目錄包含了jni模組的java介面
APP_MODULES := NDKTest --->當前模組的名字叫做NDKTest
(4)弄清楚java程式的包層次
以當前的這個project為例,就是上面代碼中的package com.TWM.NdkTest,定義的類名為NDKJni。因此,根據這個包的層次,可以根據jni檔案的函數命名規則定義函數:
JNIEXPORT jint JNICALL Java_com_TWM_NdkTest_NDKJni_MyFunc(JNIEnv * env, jobject thiz, jint a, jint b) ;
當然,手工根據包層次定義jni函數還是很痛苦的,可以藉助於javah工具:
mkdir -p apps/NDKTest/project/jni
cd apps/NDKTest/project/jni
javah -classpath "../bin/classes" com.TWM.NdkTest.NDKJni
然後就會自動產生一個叫做com_TWM_NdkTest_NDKJni.h的檔案,裡面的內容基本上跟手工產生的差不多:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_TWM_NdkTest_NDKJni */
#ifndef _Included_com_TWM_NdkTest_NDKJni
#define _Included_com_TWM_NdkTest_NDKJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_TWM_NdkTest_NDKJni
* Method: MyFunc
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_TWM_NdkTest_NDKJni_MyFunc
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
好了,有了這個函數的定義,就可以準備去編寫jni了。
(5)進入source目錄
建立目錄ndktest,然後在裡面放置兩個檔案,一個是隨便命名例如a.c,另外一個是一個叫做Android.mk的編譯指令檔。
裡面的內容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := NDKTest --->這裡是指定jni模組的名字,產生的so庫應該叫做libNDKTest.so,這個名字一定要與Application.mk檔案中的APP_MODULES相同。
LOCAL_SRC_FILES := a.c
include $(BUILD_SHARED_LIBRARY) --->這裡是告訴編譯指令碼產生的庫是共用庫(本身NDK是可以產生動態庫和靜態庫的)。
然後在a.c裡面寫入的內容如下:
JNIEXPORT jint JNICALL
Java_com_TWM_NdkTest_NDKJni_MyFunc(JNIEnv * env, jobject thiz, jint a, jint b)
{
return a+b ;
}
(6)開始編譯jni模組
首先進入android-ndk-1.5_r1目錄,然後運行如下命令:
make APP=NDKTest [斷行符號]
這個時候就會看到它開始編譯並且在apps/NDKTest/projects/目錄下建立了libs/armeabi/目錄,並且把產生的libNDKTest.so拷貝到該目錄下。
看到這裡或許有人會問,它的編譯參數怎麼沒有,我怎麼調試阿?!其實很簡單,只要多加一個編譯參數即可。
make APP=NDKTest V=1 [斷行符號]
你就會看到如下的輸出(偶的測試程式裡面把上面說的a.c改成NDKTest.c了,所以看到的內容略有不同):
wayne@wayne:~/android-ndk-1.5_r1$ make APP=NDKTest V=1
Android NDK: Building for application 'NDKTest'
Compile thumb : NDKTest <= sources/ndktest/NDKTest.c
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-gcc -Ibuild/platforms/android-1.5/arch-arm/usr/include -march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -Isources/ndktest -DANDROID -O2 -DNDEBUG -g -c -MMD -MP -MF out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d.tmp sources/ndktest/NDKTest.c -o out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o
build/core/mkdeps.sh out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d.tmp out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d
SharedLibrary : libNDKTest.so
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-gcc -nostdlib -Wl,-soname,libNDKTest.so -Wl,-shared,-Bsymbolic out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o -Wl,--whole-archive -Wl,--no-whole-archive build/platforms/android-1.5/arch-arm/usr/lib/libc.so build/platforms/android-1.5/arch-arm/usr/lib/libstdc++.so build/platforms/android-1.5/arch-arm/usr/lib/libm.so -Wl,--no-undefined -Wl,-rpath-link=build/platforms/android-1.5/arch-arm/usr/lib /home/wayne/android-ndk-1.5_r1/build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a -o out/apps/NDKTest/android-1.5-arm/libNDKTest.so
Install : libNDKTest.so => apps/NDKTest/project/libs/armeabi
mkdir -p apps/NDKTest/project/libs/armeabi
install -p out/apps/NDKTest/android-1.5-arm/libNDKTest.so apps/NDKTest/project/libs/armeabi/libNDKTest.so
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-strip --strip-debug apps/NDKTest/project/libs/armeabi/libNDKTest.so
(7)開始編譯android本地java程式
進入apps/NDKTest/project目錄,然後運行ant debug來產生調試版本的apk包,注意,此時,apk包裡面會自動把剛剛產生的libNDKTest.so打包進去的。這一點可以通過把apk檔案用unzip命令解包來驗證,在此不再贅述。
這就是android-ndk編譯jni程式的全過程,確實要比偶在上一篇文章中描述的方法來得簡單許多,總結一下:
(a)在apps目錄裡面建立帶有native關鍵字聲明的java項目。(注意,目錄需要多打一層,用來放Application.mk檔案)
(b)在sources目錄裡面建立真正的jni模組目錄,裡面一定要包含一個叫做Android.mk的檔案。
(c)在apps裡面的Application.mk與sources目錄裡面的Android.mk在MODULE的名字上一定要“遙相呼應”。
(d)編譯的方法是,進入android-ndk-1.5_r1目錄,運行make APP=<your app name> V=1產生jni庫;其實不止如此,make APP=<your app name> clean也可以清除掉。
但願這篇文章能夠對各位致力於android開發和移植的朋友們有所協助。